Logging

Read this page in the documentation

Logging There are two logging systems and they answer different questions. Query logging — "what SQL did the ORM just run?" Configured with the logging option, in the shape Sequelize uses, so existing callbacks work unchanged. The Logger — a levelled application logger (debug/info/warn/ error) that the ORM uses for its own lifecycle messages, and that emits query / slowQuery events. Both are fed by one internal path, so every data operation logs — including count, bulkCreate, increment and the aggregates, which are easy to miss when logging is bolted onto only the obvious call sites. Note: sync() is the exception. Its DDL does not reach the logging callback at all — verified by capturing every statement across a full lifecycle, where sync({ force: true }) contributed none while create, bulkCreate, findAll, count, update, destroy and a raw query each contributed theirs. What sync emits instead is the built-in logger's descriptions — Created table: users — not the SQL. If you need to see the DDL, generate it through the query interface rather than expecting it in your query log. Query logging logging value | Effect | --- | --- | false | Nothing is logged. | true | Built-in logger writes to console.log. | a function | Called as (sql, timing). | unset | Quiet — unless benchmark: true, which turns the built-in logger on. | The built-in logger's line mirrors Sequelize's format so existing log parsers keep working: Executed (<model>) names the model, or Executed (default) for a raw query. The ; [...] parameter list appears with logQueryParameters, and the elapsed time with benchmark. A custom function gets the raw SQL, undecorated — decorating it would break callers that parse or re-format the statement themselves. The timing is always passed, even without benchmark. Per-query overrides logging on a single query wins over the instance setting, in both directions: false at either level wins over a function at the other — silencing is treated as the more specific intent. The Logger Level | Value | Shows | --- | --- | --- | debug | 0 | everything | info | 1 | info and above (the default) | warn | 2 | warnings and errors | error | 3 | errors only | quiet | 4 | nothing | Method | Purpose | --- | --- | debug / info / warn / error | Log at a level, with optional structured meta. | setLevel / getLevel / getLevelName / isLevelEnabled | Level control. | logQuery(sql, duration) | Feed a query in — emits query, and slowQuery past the threshold. | setLogSQL / setSlowQueryThreshold | Adjust at runtime. | child(meta) | A logger that stamps meta onto every entry. | Output goes to the console with colour and an ISO timestamp; pass a logging function in the options to route it somewhere else (a file, a JSON transport, your own logger). createSilentLogger() gives a logger that swallows everything — the right thing in tests. getDefaultLogger() / setDefaultLogger() / resetDefaultLogger() manage the process-wide instance. Important: those three manage the default logger, which a Prorm instance does not use — each instance builds its own from its connection options. setDefaultLogger() therefore will not capture a connection's lifecycle messages; configure the connection itself, or reach its logger with prorm.getLogger(). On a connection: prorm.getLogger() returns the instance's logger, and prorm.setLoggingLevel('debug' | 'info' | 'warn' | 'error') adjusts it. Events Every query emits events on the Prorm instance, whether or not logging is on — the right hook for metrics, tracing, and alerting: slowQuery fires when a query's duration exceeds slowQueryThreshold (default 1000 ms), set on the connection options. The Logger emits its own log, query, slowQuery and error events, if you would rather listen there. Sending it to a real logger console.log is fine locally and wrong in production: no levels, no structure, nothing an aggregator can query. Both logging systems take a function, so routing them into pino, winston or bunyan is a one-liner each. Turn colors off whenever the destination is not a terminal. The default is on, and escape codes in a JSON log field are an unpleasant thing to discover later. Correlating a query with a request The useful question in production is rarely "what SQL ran" — it is "what SQL ran for this request". A child logger stamps context onto every entry: To attach that context to the queries themselves, pass a per-request logging function. AsyncLocalStorage carries it without threading an argument through every layer: Every statement now carries the request that caused it, including the ones you did not write by hand — the association loads, the hook writes, the migrations. Recipes Which queries does this endpoint run? Turn logging on for one request rather than the whole process: Why is this slow? Log only the queries that are, and leave everything else quiet: Is the ORM doing more queries than I think? Count them instead of reading them — the answer to an N+1 suspicion is a number: What is it doing during startup? Drop the level for a moment: In production Route query logging to debug, not info. It is one line per statement; at info it will dominate the log volume and cost. Leave logQueryParameters off. See Redacting values. Prefer events to log parsing for metrics. on('query') gives you the duration as a number; scraping it back out of a formatted line does not. Set colors: false anywhere the output is not a terminal. Sample rather than silence if volume is the problem — logging one query in a hundred still shows the shape of the traffic. In tests A logger that swallows everything keeps test output readable: That quiets anything using the process-wide logger. A Prorm instance builds its own, so silence it where you construct it: To assert on what was logged, collect into an array instead: Asserting on the SQL is the only way to catch a change in what the ORM emits — a test that only checks the returned rows passes just as happily when the query underneath doubles in cost. Redacting values logQueryParameters prints bound values, which will include passwords, tokens and personal data. Keep it off in production, or filter in your own callback: For an audit trail of who changed what — a different problem from statement logging — see Audit logging. For structured, policy-driven capture of access to sensitive columns, see Compliance. Related reading Audit logging — row-level change history Query optimization — the standalone SlowQueryLogger with percentiles and alerting Error handling