agentsclimarketplace

Structured logging

Skill almasumdev/awesome-mobile-observability-agent-skills/.github/skills/logs/structured-logging

Agent skills for logging, metrics, tracing, crash reporting, and analytics in mobile apps.

Install
npx -y skills add almasumdev/awesome-mobile-observability-agent-skills --skill structured-logging

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Emit JSON-structured logs on mobile with correlation ids, consistent field names, and redaction. Use when setting up logging in Kotlin, Swift, Dart, or TypeScript mobile code.

SKILL.md

6.4 KB, as published. Nobody here has run it

Structured Logging on Mobile

Instructions

Unstructured Log.d("user=${email}") logs are a debugging trap and a privacy landmine. Emit a small, typed, JSON schema everywhere and redact at the emit site.

1. The Minimum Log Schema

Every log line, on device or shipped, must conform to this schema:

{
  "ts": "2026-04-19T14:03:11.201Z",
  "level": "info",
  "logger": "checkout.payment",
  "msg": "payment_submitted",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "session_id": "s_8a92",
  "app_version": "4.12.0+4120",
  "platform": "android",
  "attrs": { "amount_cents": 1299, "currency": "USD" }
}

Rules:

  • msg is a stable event name (snake_case), not a free-text sentence. Free-text goes in attrs.detail.
  • trace_id / span_id come from the active OpenTelemetry context (see trace-propagation).
  • attrs is a flat map of primitives; nested objects are serialized into it via dot keys (attrs.user.bucket = "a").

2. Android (Kotlin)

Use a thin facade over Timber so every call serializes through one formatter:

object Log {
    private val json = Json { encodeDefaults = true }

    fun event(logger: String, msg: String, level: Level = Level.INFO, attrs: Map<String, Any?> = emptyMap()) {
        val span = Span.current().spanContext
        val entry = LogEntry(
            ts = Instant.now().toString(),
            level = level.name.lowercase(),
            logger = logger,
            msg = msg,
            traceId = span.traceId.takeIf { it != TraceId.getInvalid() },
            spanId = span.spanId.takeIf { it != SpanId.getInvalid() },
            sessionId = Session.current.id,
            appVersion = BuildConfig.VERSION_NAME + "+" + BuildConfig.VERSION_CODE,
            platform = "android",
            attrs = Redactor.scrub(attrs)
        )
        Timber.tag(logger).log(level.android, json.encodeToString(entry))
    }
}

3. iOS (Swift)

Use os.Logger for device logs and a second sink for remote:

import OSLog

struct AppLog {
    static func event(logger: String, msg: String, level: OSLogType = .info,
                      attrs: [String: Any] = [:]) {
        let scrubbed = Redactor.scrub(attrs)
        let entry = LogEntry(ts: .now, level: level.name, logger: logger,
                             msg: msg, traceId: currentTraceId(),
                             spanId: currentSpanId(), sessionId: Session.current.id,
                             appVersion: Bundle.main.version,
                             platform: "ios", attrs: scrubbed)
        Logger(subsystem: Bundle.main.bundleIdentifier!, category: logger)
            .log(level: level, "\(entry.asJSONString, privacy: .public)")
        RemoteLogSink.shared.enqueue(entry)
    }
}

privacy: .public is only safe because the payload has already been redacted. Never pass a raw \(user.email).

4. Flutter (Dart)

class AppLog {
  static void event(String logger, String msg,
      {Level level = Level.info, Map<String, Object?> attrs = const {}}) {
    final ctx = Sentry.getSpan()?.context;
    final entry = LogEntry(
      ts: DateTime.now().toUtc(),
      level: level.name,
      logger: logger,
      msg: msg,
      traceId: ctx?.traceId.toString(),
      spanId: ctx?.spanId.toString(),
      sessionId: Session.current.id,
      appVersion: '${packageInfo.version}+${packageInfo.buildNumber}',
      platform: Platform.operatingSystem,
      attrs: Redactor.scrub(attrs),
    );
    developer.log(jsonEncode(entry), name: logger, level: level.value);
    RemoteLogSink.instance.enqueue(entry);
  }
}

5. React Native (TypeScript)

export function logEvent(
  logger: string,
  msg: string,
  level: LogLevel = 'info',
  attrs: Record<string, unknown> = {},
): void {
  const span = api.trace.getActiveSpan()?.spanContext();
  const entry: LogEntry = {
    ts: new Date().toISOString(),
    level,
    logger,
    msg,
    trace_id: span?.traceId,
    span_id: span?.spanId,
    session_id: Session.current.id,
    app_version: `${pkg.version}+${buildNumber}`,
    platform: Platform.OS,
    attrs: redact(attrs),
  };
  if (__DEV__) console.log(JSON.stringify(entry));
  remoteLogSink.enqueue(entry);
}

6. Correlation Ids

  • trace_id / span_id: taken from the active OTel context so logs and traces line up.
  • session_id: generated once per app cold start, persisted in memory only, rotated on sign-out.
  • request_id: set by the HTTP interceptor on outbound requests and attached as attrs.request_id.
  • user_id_hash: a SHA-256 of the authenticated user id with an app salt, attached only after sign-in.

7. Redaction

Put a single Redactor.scrub function in front of every emit. It should:

  1. Drop any key in the deny-list (email, phone, token, password, pan, cvv, ssn, address, lat, lng).
  2. Drop any value matching a regex for emails, credit-card numbers, JWTs, or e.g. ^\+?\d{10,}$ phone patterns.
  3. Truncate free-text fields to 256 chars.
  4. Replace removed values with the string "[redacted]" so reviewers can see the shape.

Test the redactor in unit tests -- it's the single most important piece of the logger.

8. Logger Names

  • Use dotted hierarchical names: checkout.payment, auth.oauth, home.feed.
  • Filter by logger name in the remote pipeline to downsample chatty loggers before ingestion.
  • Document reserved prefixes in Agent.md so feature teams do not collide.

9. Dashboards

  • A "log volume per logger per release" panel catches a regression where someone added Log.event inside a render loop.
  • An "error log rate vs crash-free sessions" correlation chart validates that error logs line up with actual crashes.

Checklist

  • Single facade (Log.event / AppLog.event) is the only way to emit logs in the codebase.
  • Every log conforms to the JSON schema with ts, level, logger, msg, trace_id, session_id, app_version, platform, attrs.
  • msg is a stable snake_case event name, not free text.
  • Redactor unit tests cover email, phone, token, credit card, and arbitrary denylisted keys.
  • OTel trace/span ids are attached automatically.
  • Session id rotates on sign-out and is never persisted to disk.
  • Log volume dashboards are in place to catch regressions.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.