Structured logging
Skill almasumdev/awesome-mobile-observability-agent-skills/.github/skills/logs/structured-logging
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.From its SKILL.md
npx -y skills add almasumdev/awesome-mobile-observability-agent-skills --skill structured-loggingAssembled 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.
SKILL.md
6.4 KB, ~1.6k tokens by cl100k_base, 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:
msgis a stable event name (snake_case), not a free-text sentence. Free-text goes inattrs.detail.trace_id/span_idcome from the active OpenTelemetry context (seetrace-propagation).attrsis 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:
- Drop any key in the deny-list (
email,phone,token,password,pan,cvv,ssn,address,lat,lng). - Drop any value matching a regex for emails, credit-card numbers, JWTs, or e.g.
^\+?\d{10,}$phone patterns. - Truncate free-text fields to 256 chars.
- 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.mdso feature teams do not collide.
9. Dashboards
- A "log volume per logger per release" panel catches a regression where someone added
Log.eventinside 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. -
msgis 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.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.