observability-and-logging — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited observability-and-logging (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The operability layer every long-running service needs: how it logs, how it reports health, and how it survives a failing dependency. The cross-cutting principles hub is the coding-standards skill; this skill is the detailed playbook for the logging and observability rules it points to.
must not touch the call sites.
error with the full stack trace for failures. Do not log at error for an expected, handled condition.
request can be followed end to end across services.
printed or logged.
never emit at that level when building it is expensive; guard it or let the logger evaluate lazily.
The error-handling rules (specific exception types, chaining to preserve the cause, central handling, structured error shape) live in the coding-standards hub. This skill covers how those errors are logged, not how they are raised.
bolt-on.
dependencies. A readiness check verifies the database, cache, and external dependencies and reports unhealthy when one is degraded. Keep both outside the versioned API path.
actually constrain the service. A dashboard nobody acts on is noise.
not on every transient blip.
dependency.
count. Never retry a non-idempotent operation without an idempotency key.
instead of exhausting the caller's threads and connections.
Handle shutdown deliberately: stop accepting new work, finish or cancel in-flight work, close connections and flush buffers, then exit. Set a hard timeout as a backstop so a stuck task cannot block the shutdown forever.
Every long-running service emits one canonical multi-line INFO log entry the moment it starts accepting traffic, not the moment the process boots. Use the latest-possible startup hook for the framework. This single line is the most-read log entry in any service: first thing on-call sees when paging, first thing CI inspects when smoke-testing, first thing a new contributor sees when running locally. Investing in its clarity pays back every shift.
The log block contains, in order:
█▀▄╔╗╚╝═║). Width ~120chars, 6 lines. Modern terminals, container stdout, Loki, JsonEncoder-escaped JSON logs all render it. The bold weight makes "we're up" unmistakable when scrolling startup output. Do not use Standard / Big / Slant / Small / Block ASCII FIGlet: they pack too tightly to scan from a kubectl logs flood, and look indistinguishable from any other log line. Plain-ASCII fallback is acceptable only when the deployment target is a known UTF-8-hostile environment (legacy cmd.exe, embedded serial console).
Generate the banner once at build time, paste it into a string constant. Do NOT compute it at runtime, do NOT have the agent "draw" it freehand (the agent will silently pick a different FIGlet font, usually Standard, every time). Use one of:
figlet -f 'ANSI Shadow' 'EXAMPLE'This is the canonical look for the app name EXAMPLE:
███████╗██╗ ██╗ █████╗ ███╗ ███╗██████╗ ██╗ ███████╗
██╔════╝╚██╗██╔╝██╔══██╗████╗ ████║██╔══██╗██║ ██╔════╝
█████╗ ╚███╔╝ ███████║██╔████╔██║██████╔╝██║ █████╗
██╔══╝ ██╔██╗ ██╔══██║██║╚██╔╝██║██╔═══╝ ██║ ██╔══╝
███████╗██╔╝ ██╗██║ ██║██║ ╚═╝ ██║██║ ███████╗███████╗
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝Tells that the wrong font was used: the banner is ~3 lines tall instead of 6, uses / \ _ | ASCII slashes instead of Unicode box-drawing, looks like _ _____ _____ ____ ____ patterns, or fits inside a single 80-column line. Regenerate with the FIGlet font explicitly set to ANSI Shadow.
Local: http://localhost:port/... and Hostname: http://<resolved-hostname>:port/.... Both arediagnostic: in Kubernetes the real external URL comes from ingress / service definitions, not the app's self-report.
local, docker, prod, staging).stall the banner. Result format: <url> [Connected | Warning (status=N) | FAILED]: URL first, status marker last. Never include the exception detail in the banner; log it at DEBUG for diagnostics.
Swagger UI / tracing endpoint + sampling, plus the logging encoder mode by profile.
Canonical full output, what a real service should print when it accepts traffic. Match this layout precisely; omit sections that don't apply (e.g. no Auth section if the service is unauthenticated), but keep the ones that do in this order:
███████╗██╗ ██╗ █████╗ ███╗ ███╗██████╗ ██╗ ███████╗
██╔════╝╚██╗██╔╝██╔══██╗████╗ ████║██╔══██╗██║ ██╔════╝
█████╗ ╚███╔╝ ███████║██╔████╔██║██████╔╝██║ █████╗
██╔══╝ ██╔██╗ ██╔══██║██║╚██╔╝██║██╔═══╝ ██║ ██╔══╝
███████╗██╔╝ ██╗██║ ██║██║ ╚═╝ ██║██║ ███████╗███████╗
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝
----------------------------------------------------------
Application 'example' is running!
Access URLs:
Local: http://localhost:8080/example
Hostname: http://hostname:8080/example
Profile(s): local
Auth (OAuth2 Resource Server):
Issuer: https://keycloak.example/realms/main
JWK Set: https://keycloak.example/realms/main/protocol/openid-connect/certs [Connected]
Roles: @MainUser → hasAnyRole('USER','ADMIN')
Service Discovery:
Eureka: [Disabled] (spring.cloud.discovery.enabled=false)
Database:
Postgres: jdbc:postgresql://db.internal:5432/example [Connected]
Actuator:
Health: http://localhost:8080/example/actuator/health
Readiness: http://localhost:8080/example/actuator/health/readiness
Prometheus: http://localhost:8080/example/actuator/prometheus
Metrics: http://localhost:8080/example/actuator/metrics
API documentation:
OpenAPI: http://localhost:8080/example/openapi/v3/api-docs
Swagger UI: http://localhost:8080/example/openapi/swagger-ui.html
Observability:
Tracing: OTel bridge enabled, no OTLP endpoint set (sampling=1.0)
Logging: text pattern [traceId,spanId,jwt] (local/test profile)
----------------------------------------------------------Format rules:
- repeated). Same character, same count. Section name:), keys inside asection are 3-indented ( Key: value).
Local: ,Hostname: ).
[Connected], [Warning (status=N)], [FAILED], [Disabled]. Neverinclude the exception detail in the banner; log it at DEBUG.
#### Emit the whole block in ONE log call with a leading \n
The banner plus the readiness body must be a single log statement whose message starts with \n. The leading newline pushes the first line of the banner (and every line after it) below the framework's prefix, so the banner art renders cleanly from column 0.
Bad: one log call per line of the banner. Every line gets its own timestamp / level / logger prefix, which destroys the art:
2026-05-22 09:28:36.586 INFO c.l.s.i.config.StartupLogConfig [Example] : ███████╗██╗ ██╗ █████╗ ███╗ ███╗██████╗ ██╗ ███████╗
2026-05-22 09:28:36.587 INFO c.l.s.i.config.StartupLogConfig [Example] : ██╔════╝╚██╗██╔╝██╔══██╗████╗ ████║██╔══██╗██║ ██╔════╝
2026-05-22 09:28:36.587 INFO c.l.s.i.config.StartupLogConfig [Example] : █████╗ ╚███╔╝ ███████║██╔████╔██║██████╔╝██║ █████╗
2026-05-22 09:28:36.587 INFO c.l.s.i.config.StartupLogConfig [Example] : ██╔══╝ ██╔██╗ ██╔══██║██║╚██╔╝██║██╔═══╝ ██║ ██╔══╝
2026-05-22 09:28:36.587 INFO c.l.s.i.config.StartupLogConfig [Example] : ███████╗██╔╝ ██╗██║ ██║██║ ╚═╝ ██║██║ ███████╗███████╗
2026-05-22 09:28:36.587 INFO c.l.s.i.config.StartupLogConfig [Example] : ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝Good: one log call, leading \n, banner art rendered cleanly underneath the single prefix line:
2026-05-22 09:28:36.586 INFO c.l.s.i.config.StartupLogConfig [Example] :
███████╗██╗ ██╗ █████╗ ███╗ ███╗██████╗ ██╗ ███████╗
██╔════╝╚██╗██╔╝██╔══██╗████╗ ████║██╔══██╗██║ ██╔════╝
█████╗ ╚███╔╝ ███████║██╔████╔██║██████╔╝██║ █████╗
██╔══╝ ██╔██╗ ██╔══██║██║╚██╔╝██║██╔═══╝ ██║ ██╔══╝
███████╗██╔╝ ██╗██║ ██║██║ ╚═╝ ██║██║ ███████╗███████╗
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚══════╝╚══════╝
----------------------------------------------------------
Application 'example' is running!
...Stack-by-stack:
log.info("\n{}", buildStartupLog()); (placeholder substitutes the entire string).logger.info('\n' + buildStartupLog()).logging: logger.info("\n" + build_startup_log()).slog: logger.Info("\n" + buildStartupLog()).cat <<'EOF' / Write-Host print directly to stdout.Never split the banner across multiple log calls. Never call println / fmt.Print / Write-Host from inside a log handler that adds a prefix per call.
Equally important: the builder must return one multi-line String. Building it via repeated log.info(line) calls is the same bug from a different angle. Per-call emission is non-atomic, so other threads' logs (background pools, Axon coordinators, scheduled jobs) can interleave between your readiness lines and rip the block apart visually.
Wrong (Java, per-line emission):
log.info("----------------------------------------------------------");
log.info(" Application '{}' is running!", appName);
log.info("");
log.info(" Access URLs:");
log.info(" Local: {}", localUrl);
log.info(" Hostname: {}", hostnameUrl);
// ...one log call per line, each gets its own timestamp + level + logger prefix,
// and a Coordinator / scheduler / pool thread can log between any two of these.Right (Java, build then emit once):
String block = String.join("\n",
"----------------------------------------------------------",
" Application '" + appName + "' is running!",
"",
" Access URLs:",
" Local: " + localUrl,
" Hostname: " + hostnameUrl,
// ... rest of sections
"----------------------------------------------------------"
);
log.info("\n{}", block);
// One prefix line, the rest of the block flows under it at column 0. Atomic;
// nothing can interleave between sections.Same shape for every framework: build the whole block first (StringBuilder, String.join, template literal, Python f-string, Go strings.Builder), then emit once. If the builder needs to probe dependencies asynchronously, do that work first, collect the results, then assemble the string, then log.
Pick the framework's "we're really up" hook from its own skill: springboot-patterns, backend-patterns, python-patterns, golang-patterns, bash, powershell. The hook differs per stack; the convention above is identical.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.