Skip to content
AI-assisted, verified against source

Logging

NpgsqlRest logging is built on Serilog and has three independent axes:

  • Channels (Serilog source contexts) — who is logging: the endpoint engine, the client host, the test runner, the .NET framework. Each channel's level is set independently under Log:MinimalLevels.
  • Sinkswhere logs go: console, rolling files, a PostgreSQL command, an OpenTelemetry collector. Each sink has its own minimum level on top of the channel levels.
  • LevelsVerbose < Debug < Information < Warning < Error < Fatal, plus "Off" (since 3.19.0) to silence a channel entirely.

This guide is the task-oriented walkthrough; the option-by-option reference is at Logging configuration.

The channel map

Knowing which channel carries what — and at which level — answers most "how do I see X?" questions:

ChannelWho logs on itDebug showsVerbose adds
NpgsqlRestThe endpoint engine (library + plugins)Every endpoint as it is created, with each annotation applied (authorize, cached, path, …)pg_catalog discovery queries, the SQL-file describe phase, and — with LogCommandsevery SQL command endpoints execute
NpgsqlRestClient (displayed as your ApplicationName when set)The client hostConfiguration processing, auth setup, startup detail
NpgsqlRestTestThe SQL test runner (--test)Test discovery and parsingEvery test statement and in-process HTTP invocation
Microsoft, SystemASP.NET Core / .NETFramework internals (defaults to Warning — keep it there)

PostgreSQL itself participates too: anything your SQL raises (raise info 'x', raise warning 'y') flows into the logs — see PostgreSQL messages in your logs.

json
json
{
  "Log": {
    "MinimalLevels": {
      "NpgsqlRest": "Information",
      "NpgsqlRestClient": "Information",
      "NpgsqlRestTest": "Information",
      "System": "Warning",
      "Microsoft": "Warning"
    }
  }
}

Channel names and ApplicationName

When ApplicationName is set, the client host channel takes that name in the log output — lines show [MyApi] instead of [NpgsqlRestClient] in the {SourceContext} template placeholder. The configuration key stays stable: "MinimalLevels": { "NpgsqlRestClient": ... } keeps working regardless, because the client maps it to the actual channel name for you (using the application name itself as the key also works). The core engine channel is always NpgsqlRestApplicationName never affects it. The test runner channel name is configurable via TestRunner.LoggerName.

Any setting works from the command line as well: npgsqlrest --log:minimallevels:npgsqlrest=debug.

Recipes

See which endpoints exist and why

json
json
{ "Log": { "MinimalLevels": { "NpgsqlRest": "Debug" } } }
code
[DBG] Function public.get_users mapped to GET /api/get-users has set AUTHORIZE by the comment annotation with roles: admin
[DBG] Created endpoint GET /api/get-users

This is the first thing to reach for when an annotation seems ignored or an endpoint is missing. (To just list endpoints without starting the server: npgsqlrest --endpoints.)

See every SQL command endpoints execute

Two switches — LogCommands opts in, and the channel must be at Verbose (commands log at trace level):

json
json
{
  "NpgsqlRest": { "LogCommands": true },
  "Log": { "MinimalLevels": { "NpgsqlRest": "Verbose" } }
}

Add "LogCommandParameters": true to include parameter values — invaluable in development, but treat it as sensitive in production (passwords and personal data end up in logs; the @security_sensitive annotation obfuscates a specific endpoint's parameters).

Debug discovery: "why isn't my function/file picked up?"

json
json
{ "Log": { "MinimalLevels": { "NpgsqlRest": "Verbose" } } }

Verbose shows the raw pg_catalog discovery queries with their schema/name filters, and the SQL-file describe phase — you can see exactly what was scanned and what was skipped, and why.

Watch the test runner, mute everything else

json
json
{
  "Log": {
    "MinimalLevels": {
      "NpgsqlRest": "Off",
      "NpgsqlRestClient": "Off",
      "NpgsqlRestTest": "Verbose"
    }
  }
}

Verbose on NpgsqlRestTest prints every test statement and every in-process endpoint invocation. See the Testing Guide for the runner itself — note that the console report (PASS/FAIL lines) is always printed regardless of log levels; TestRunner.DetailedReport shapes the report, log levels shape the diagnostics.

Silence a channel completely

Since 3.19.0, "Off" (aliases "None", "Silent") fully mutes a channel — previously the quietest option was Fatal, which still let fatal events through:

json
json
{ "Log": { "MinimalLevels": { "NpgsqlRest": "Off" } } }

PostgreSQL messages in your logs

Messages raised by your SQL — raise debug/log/info/notice/warning in functions, procedures, DO blocks, or triggers — are captured from the connection and logged on the endpoint's channel, at the level matching the PostgreSQL severity. This is on by default (LogConnectionNoticeEvents: true in the NpgsqlRest section), which turns raise into a zero-infrastructure logging facility for your database code:

sql
sql
create function transfer(from_id int, to_id int, amount numeric) returns void as $$
begin
    ...
    raise info 'transfer of % from % to % completed', amount, from_id, to_id;
end $$ language plpgsql;

Every call now leaves an INF line in the server logs — no logging table, no extension.

LogConnectionNoticeEventsMode controls the shape: MessageOnly, FirstStackFrameAndMessage (default — includes where in your PL/pgSQL the raise happened), or FullStackAndMessage.

Two related notes:

  • On SSE endpoints, raise messages at the configured notice level become events streamed to the client rather than plain log lines.
  • In the test runner, captured notices are shown under failing tests (and under passing ones with DetailedReport).

Logging into PostgreSQL

The database can be a log destination as well as a source — every log event can invoke a PostgreSQL command. You own the command and therefore the schema:

sql
sql
create table logs (
    at timestamptz not null,
    level text not null,
    message text not null,
    exception text,
    source text
);

create procedure log(_level text, _message text, _at timestamptz, _exception text, _source text)
language sql as $$
    insert into logs values (_at, _level, _message, _exception, _source);
$$;
json
json
{
  "Log": {
    "ToPostgres": true,
    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    "PostgresMinimumLevel": "Warning"
  }
}

The five positional parameters are: level, message, UTC timestamp, exception text (or null), and the source context (channel name) — see the reference. Since it's your procedure, you can route, enrich, prune, or pg_notify from it.

Keep the level high

PostgresMinimumLevel: "Warning" is a sensible floor — logging every Verbose event back into the database from a busy API is a self-inflicted write load.

Files, OpenTelemetry, and production

Console output is on by default and is the right answer for containers (12-factor: let the platform collect stdout). Beyond that:

Rolling files — size-based rolling with retention:

json
json
{
  "Log": {
    "ToFile": true,
    "FilePath": "/var/log/npgsqlrest/app.log",
    "FileSizeLimitBytes": 50000000,
    "RetainedFileCountLimit": 14
  }
}

OpenTelemetry (OTLP) — ship to a collector (Grafana/Loki, Datadog, etc.), with resource attributes carrying the application name and environment:

json
json
{
  "Log": {
    "ToOpenTelemetry": true,
    "OTLPEndpoint": "http://otel-collector:4317",
    "OTLPProtocol": "Grpc",
    "OTLPResourceAttributes": {
      "service.name": "{application}",
      "service.environment": "{environment}"
    }
  }
}

Levels compose: MinimalLevels filters at the source (per channel); each sink then applies its own minimum (ConsoleMinimumLevel, FileMinimumLevel, PostgresMinimumLevel, OTLPMinimumLevel). A common production shape: channels at Information, console at Information, file at Information, PostgreSQL at Warning.

A production baseline:

json
json
{
  "Log": {
    "MinimalLevels": {
      "NpgsqlRest": "Information",
      "NpgsqlRestClient": "Information",
      "NpgsqlRestTest": "Information",
      "System": "Warning",
      "Microsoft": "Warning"
    },
    "ToConsole": true,
    "ConsoleMinimumLevel": "Information",
    "ToFile": true,
    "FilePath": "/var/log/npgsqlrest/app.log",
    "FileMinimumLevel": "Information",
    "ToPostgres": true,
    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    "PostgresMinimumLevel": "Warning"
  }
}

And the development counterpart:

json
json
{
  "Log": {
    "MinimalLevels": { "NpgsqlRest": "Debug" }
  }
}

Comments