Developer Guidelines

These guidelines standardize how to write application logs across our backend services. Application logs should complement our infrastructure logs and APM to support troubleshooting in production. Our applications already emit standardized startup logs, as well as logs for inbound and outbound requests. As a developer, focus on logging actionable insights with a consistent structure and minimal noise.

These guidelines apply to island.is application logs, usually logged through a Winston logger object. Audit logs have different goals and principles.

Principles

  • Log with purpose: every log line should help diagnose, alert, or audit.

  • Prefer observability over verbosity: use metrics/traces for high-volume signals, logs for sparse, contextual events.

  • Protect users: never log secrets, sensitive PII, or regulated data.

  • Make correlation easy: ensure logs can be tied to traces, requests, and users without exposing sensitive data.

What to log?

Log sparse, high-signal events that help you reconstruct what happened, when, and why—without duplicating what existing logs / traces already cover.

  • Domain milestones and state changes.

  • Background work.

  • External dependencies and resilience.

  • Unexpected but handled conditions.

  • Context, ownership and stable non-sensitive identifiers for correlation.

Rule of thumb: log milestones and state transitions, not every step. If it’s actionable for on-call or essential to reconstruct a timeline—and not already covered by standard request/startup logs—log it.

What not to log?

  • Secrets: passwords, tokens, API keys, client secrets, private keys, session IDs.

  • Personal data: kennitala, addresses, emails, phone numbers, full names, date of birth, financial details, health data.

  • Full request/response bodies, especially for auth or external providers.

  • Large objects/binaries, stack traces on known/handled errors, repeated identical messages in loops.

  • Anything already logged by standard handlers (avoid duplicates).

How to log?

Use the appropriate log level

Debug

Use for developer diagnostics and detailed control flow. Typically visible only in local environments, but may be selectively enabled in production for short periods.

Example:

Info

Use for lifecycle events, domain events and meaningful state changes in production (e.g., service lifecycle, migrations, record changes, job processing, circuit closed, retry).

Be sure to include stable identifiers (non-sensitive) for correlation.

Example:

Warning

You should use warnings for unusual but handled conditions that may need attention (fallbacks, unexpected input, degraded mode, circuit open).

Example:

Error

Log unexpected failures that require action or indicate deviating behavior.

In most cases, don’t log errors directly—let them bubble up to the standard error handler. The key exception is when you must catch all errors to apply fallback logic; in that case, log the unexpected errors at this level.

See more about handling and logging errors below.

Example:

Group logs with context and ownership

Use two stable fields on every log to make filtering and ownership clear:

  • context: Identifies the producing component (e.g., SomeService, OrdersController, PaymentClient). Create a child logger per component so every log from it carries the same context.

  • codeOwner: Identifies the development team responsible for the service, endpoint or resource (eg application template).

How to set codeOwner:

  • Service-wide (infra): app.codeOwner(CodeOwners.Name) — applies to the entire backend service.

  • Endpoint/class level: @CodeOwner(CodeOwners.Name) — on a controller, handler, or resolver.

  • Call scope: withCodeOwner(CodeOwners.Name, () => {}) — wraps a specific function or record-level operation.

Example:

You may also provide custom context fields to multiple log entries using withLoggingContext:

How to log errors

Remember to think twice before logging errors (see below).

JavaScript Error objects have non-enumerable fields (e.g., stack) that won’t appear if you spread them into fields. Log the Error as the log message so the formatter can include those fields.

How to catch and throw new errors

When you need to translate an unexpected error into a different one (e.g., to drive frontend behavior or enrich request-level handling), wrap the original error using the cause option. This preserves the original error for logs while keeping it hidden from clients.

Think twice before logging errors

Generally, you should let unexpected errors bubble up to our request middleware, where they’ll be handled and logged. This centralizes error handling and prevents duplicate logs.

Different error types require different treatment.

Did it happen because of invalid user input?

Errors caused by invalid input should almost never be logged at the Error level to avoid noise—regardless of whether the bad input came from a user, a frontend bug, or environmental issues (e.g., outdated browser, network changes).

If unexpected input triggers an error in a library, database, or external API, catch it and rethrow a 4xx error (e.g., ValidationFailedProblem) so the middleware does not log it as an error. By default, 4xx errors are not logged by our middleware.

Before throwing a 4xx error, you may log the event at Info or Warn:

  • Use Info for anticipated validation failures or common user mistakes.

  • Use Warn when the input is unexpected or indicates a potential upstream/client issue.

Did it happen in an outgoing request?

Use our Enhanced Fetch library for all API integrations; it provides standardized error handling and logging for outbound requests.

  • Don’t catch and log unexpected errors from outbound requests. Let them bubble up to the request middleware for consistent handling and logging.

  • Do catch expected outcomes, such as 4xx responses caused by invalid input. When appropriate, use handle204 or handle404 to return null instead. (See the “Invalid user input” section above.)

  • Don’t blanket-rethrow all 4xx responses. Some (e.g., 401) may indicate an environment/configuration issue and should bubble up to be handled and logged as unexpected errors.

When you must handle outbound errors to provide a fallback or degraded response, log the original error at the appropriate level:

  • Network failures and 5xx responses: log at Error.

  • 4xx responses: log at Warn (unless you’ve determined they represent a broader incident, in which case Error is appropriate).

Standard fields

timestamp

Time of the log entry.

Automatic (Logger)

level

Debug, info, warn or error depending on log method used.

Manual

message

Concise, human-readable summary of the log entry. The Error message when logging errors.

Manual

context

Which software component or class the log entry comes from.

Automatic (Child logger)

codeOwner

The team responsible for this part of our backend architecture.

Automatic (Service, Endpoint, Resource)

name

The Error name.

Automatic (Error)

stack

The Error stack trace.

Automatic (Error)

dd

Extra metadata provided by datadog agent. Includes env, service and version sub fields.

Automatic (Datadog)

traceSid

Trace Session ID to correlate logs with users without exposing PII.

Automatic (Authentication)

http

Information about outgoing requests and responses. May include these subfields: status_code, status_text and url.

Automatic (Enhanced Fetch)

fetch.name

Name of the enhanced fetch integration.

Automatic (Enhanced Fetch)

organizationSlug

Reference to a government organization which a log entry or error relates to.

Automatic (Enhanced Fetch)

Last updated

Was this helpful?