Repository files navigation

@smooai/logger — Contextual logging for AWS and the browser

npmPyPIcrates.ioNuGet

Smoo AIlicense

PR Checks (all 5 languages)Release

TypeScriptPythonRustGo.NET

What it is · Feature tour · Install · Quickstart · Language matrix · Platform


A log line that only carries the message is a clue. One that carries the whole story is an answer.@smooai/logger stamps every entry with the request journey (correlation IDs across services), the AWS runtime around it (Lambda, SQS, API Gateway context), and — where an OpenTelemetry span is active — the real W3C trace and span IDs, so logs join your traces instead of floating beside them. Native ports in five languages — TypeScript, Python, Rust, Go, and .NET — emit the same JSON shape, so a request crossing language boundaries still reads as one story.

Traditional loggers give you the message, but not the story. @smooai/logger records where the log came from, the request journey that led there, and the runtime around it — so a production failure reads like a trace, not a guess.


What is this?

One structured-logging schema, implemented natively five times. Each port is idiomatic in its own language, but the wire shape — level names, correlationId, http.request/response, user, telemetry, error serialization — is shared, so logs from a TypeScript Lambda, a Go worker, a Python API, a Rust service, and a .NET job land in the same queries.

  • TypeScript (src/) — the original. AWS server logging plus the only browser logger (device/browser detection, fetch correlation).
  • Python (python/) — full port, plus Socket.IO and Uvicorn logging adapters.
  • Rust (rust/logger/) — serde-based port; Lambda context helpers behind an aws-lambda feature flag.
  • Go (go/) — full port on log/slog, including Lambda/SQS helpers and OTel span correlation.
  • .NET (dotnet/) — full port; integrates with Microsoft.Extensions.Logging, trace correlation via System.Diagnostics.Activity.

The ports are not all identical — the honest capability matrix is below.


Feature tour

CapabilityWhere
🔗Correlation across servicesAll 5 languages
AWS context, captured automaticallyAll 5 languages
🔭Logs that join your tracesTS · Python · Rust · Go (+ .NET via Activity)
📍Exact caller locationAll 5 languages
🎨Pretty local output + rotating file logsAll 5 languages
🕶️Sensitive-key redactionAll 5 languages
🖥️Browser loggingTypeScript only

🔗 Correlation across services

A correlation ID set (or extracted from an incoming header, Lambda event, or SQS record) follows the request everywhere, in every language:

// Service A: API Gateway handler (TypeScript)logger.addLambdaContext(event,context);logger.info("Request received");// correlationId: abc-123// Service B: SQS processor (extracts the ID from the record)logger.addSQSRecordContext(record);logger.info("Processing message");// same correlationId: abc-123
// Service C: a Go worker — same schema, same IDl.AddHTTPRequest(logger.HTTPRequest{
Headers: map[string]string{"X-Correlation-Id": "abc-123"},
})
l.Info("Completing workflow", logger.Map{"orderId": "ord_1"}) // still abc-123
%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
B["Browser<br/>BrowserLogger (TS)"] -->|"X-Correlation-Id: abc-123"| GW["API Gateway → Lambda<br/>AwsServerLogger (TS)"]
GW -->|"SQS message attributes"| Q["SQS worker<br/>(Go port)"]
Q -->|"HTTP header"| API["Internal API<br/>(Python / .NET / Rust port)"]
GW -.-> LOGS[("One query:<br/>correlationId = abc-123")]
Q -.-> LOGS
API -.-> LOGS
B -.-> LOGS
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class LOGS warm
class B teal
Loading

⚡ AWS context, captured automatically

Hand the logger the Lambda event/context (or SQS record, or HTTP request) once; every subsequent line carries the invocation metadata — function, region, request IDs, HTTP method/path/headers, SQS attributes.

import{AwsServerLogger}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "UserAPI"});exportconsthandler=async(event,context)=>{logger.addLambdaContext(event,context);try{constuser=awaitcreateUser(event.body);logger.info("User created successfully",{userId: user.id});return{statusCode: 201,body: JSON.stringify(user)};}catch(error){logger.error("Failed to create user",error,{body: event.body});throwerror;}};

The same helpers exist in each port: add_lambda_context (Python), NewLambdaLogger / AddSQSRecordContext (Go), AddLambdaContext (.NET), and Lambda environment/event helpers behind the aws-lambda feature (Rust).

🔭 Logs that join your traces

Historically every line's traceId was a fabricated UUID — useless for joining logs to traces. Now, when an OpenTelemetry span is active, TypeScript, Python, Rust, and Go stamp the span's real W3C trace_id and span_id onto the line, matching what your tracing backend recorded. No active span → the UUID fallback, unchanged.

// Go: thread the context and the active span's IDs land on the linel.InfoContext(ctx, "Order shipped", logger.Map{"orderId": "ord_1"})
// → { "traceId": "4bf92f35…", "spanId": "00f067aa…", … }

Each port depends only on the OTel API (no SDK, no exporter). .NET is the one exception: it takes no OpenTelemetry dependency at all and reads the same real W3C IDs from System.Diagnostics.Activity.Current — the API OTel .NET itself builds on — so the output is equivalent.

📍 Exact caller location

Every entry includes where in the code it was emitted, in all five languages:

{
"callerContext": {
"stack": [
"at UserService.createUser (/src/services/UserService.ts:42:16)",
"at processRequest (/src/handlers/userHandler.ts:15:23)",
],
},
}

Two shapes are in play, and the difference is deliberate:

shapeportshow
callerContext.stack — multiple framesTypeScript, Pythonwalks the runtime stack
caller: { file, line, function } — one frameGo, Rust, .NETzero-cost compile-time / runtime.Caller
{ "caller": { "file": "UserService.cs", "line": 42, "function": "CreateUser" } }

Rust omits function: #[track_caller] gives file and line for free, but std::panic::Location carries no symbol name and resolving one would mean capturing a backtrace on every line. .NET uses [CallerFilePath]/[CallerLineNumber]/[CallerMemberName], which the compiler fills in at each call site — no StackTrace walk. Both emit the file basename only; the full path is build-machine noise.

🎨 Pretty local output + rotating file logs

All five ports detect local development and switch from strict JSON lines to ANSI pretty-printing — and write logs to disk under .smooai-logs/ with size/interval-based rotation:

constlogger=newAwsServerLogger({prettyPrint: true,// auto-enabled locallyrotation: {size: "10M",interval: "1d",compress: true},});

🕶️ Sensitive-key redaction

Every port scrubs values whose keys match a redaction list (case-insensitive, recursive) before a line is emitted — password, token, authorization, and friends by default, extensible per logger (addRedactKeys / add_redact_keys / DefaultRedactKeys…).

🖥️ Browser logging

TypeScript only.BrowserLogger captures device type, browser name/version, platform, and user agent, and correlates fetches to your backend logs:

import{BrowserLogger}from"@smooai/logger/browser/BrowserLogger";constlogger=newBrowserLogger({name: "CheckoutFlow"});constresponse=awaitfetch("/api/checkout",{method: "POST",headers: {"X-Correlation-Id": logger.correlationId()},});logger.addResponseContext(response);logger.info("Checkout completed",{orderId: data.id});

📦 Install

LanguagePackageInstall
TypeScript@smooai/loggerpnpm add @smooai/logger
Pythonsmooai-loggerpip install smooai-logger (or uv add smooai-logger)
Rustsmooai-loggercargo add smooai-logger
Gogithub.com/SmooAI/logger/go/v4go get github.com/SmooAI/logger/go/v4
.NETSmooAI.Loggerdotnet add package SmooAI.Logger

🚀 Quickstart

TypeScript (the original port — see AWS context and browser above for fuller examples):

// AWS environments (Lambda, ECS, EC2, …)import{AwsServerLogger,Level}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "OrderService",level: Level.Info});logger.addUserContext({id: "user-123",role: "admin"});// persists across logslogger.addTelemetryFields({duration: 150,operation: "db-query"});logger.info("Payment processed",{amount: 99.99,currency: "USD"});try{awaitriskyOperation();}catch(error){logger.error("Operation failed",error,{context: "additional-info"});// → error message, stack trace, error type, and your context}

Six levels in every port — TRACE · DEBUG · INFO · WARN · ERROR · FATAL — plus context presets (MINIMAL / FULL).

Per-language quickstarts, with full API docs:


One schema, five ports

The wire schema is shared; port depth is not identical. Here's the honest status of each surface:

CapabilityTypeScriptPythonRustGo.NET
Structured JSON, 6 levels
Correlation / request / trace IDs
HTTP request/response context
User context + telemetry fields
Lambda / SQS / API Gateway helpers✅ ¹
Pretty local output
Rotating file logs (.smooai-logs/)
Sensitive-key redaction
OTel span → traceId/spanId stamping➖ ²
Per-line caller location ⁴
Browser logger
Parity corpus enforced in tests ³

¹ Behind the aws-lambda cargo feature; Lambda environment context needs no feature. ² No OTel dependency — equivalent real W3C trace/span IDs read from System.Diagnostics.Activity.Current, which is the API OpenTelemetry .NET itself builds on. Log lines also tee upstream via SmooLoggerOptions.ForwardTo (an ILogger), the hook OTel's .NET log appender attaches to. ³ parity-corpus.json is the cross-language output contract, and all five ports now replay it from that one committed file — TypeScript (src/parity-corpus.spec.ts), Python (python/tests/test_parity_corpus.py), Rust (rust/logger/tests/parity_corpus.rs), Go (go/parity_corpus_test.go), and .NET (dotnet/tests/SmooAI.Logger.Tests/ParityCorpusTests.cs). It covers level mapping, required field names, message shape, correlation-id propagation, and the default redaction key list. Editing a corpus value turns all five suites red. ⁴ Two shapes: TypeScript and Python emit a multi-frame callerContext.stack; Go, Rust and .NET emit a single-frame caller object. Rust's omits function — see Exact caller location.

CI does cover all five languages on every PR (pr-checks.yml typechecks, lints, tests, and builds TS, Python, Rust, Go, and .NET), and release.yml publishes all five: npm → PyPI → crates.io → Go module tag → NuGet.


🔎 Looking for the desktop Log Viewer?

It moved. The Rust/egui log viewer that used to live here has been rebuilt as SmooAI Observability Studio — a Dioxus native desktop client for SmooAI logs, errors, and metrics — and lives in the SmooAI/observability repo (desktop/), with builds on its releases page. The crate has been deleted from this repo; see log-viewer/DEPRECATED.md for the migration story.

🧩 Part of Smoo AI

@smooai/logger is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

Use them in your stack, or take them as a reference for how we build.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases — add one with pnpm changeset, then open a pull request referencing any related issues.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Multi-language structured logging (TypeScript, Python, Rust, Go) for AWS Lambda and browser — correlation tracking, automatic context gathering, and a Rust/egui desktop log viewer.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

@smooai/logger — Contextual logging for AWS and the browser

npmPyPIcrates.ioNuGet

Smoo AIlicense

PR Checks (all 5 languages)Release

TypeScriptPythonRustGo.NET

What it is · Feature tour · Install · Quickstart · Language matrix · Platform


A log line that only carries the message is a clue. One that carries the whole story is an answer.@smooai/logger stamps every entry with the request journey (correlation IDs across services), the AWS runtime around it (Lambda, SQS, API Gateway context), and — where an OpenTelemetry span is active — the real W3C trace and span IDs, so logs join your traces instead of floating beside them. Native ports in five languages — TypeScript, Python, Rust, Go, and .NET — emit the same JSON shape, so a request crossing language boundaries still reads as one story.

Traditional loggers give you the message, but not the story. @smooai/logger records where the log came from, the request journey that led there, and the runtime around it — so a production failure reads like a trace, not a guess.


What is this?

One structured-logging schema, implemented natively five times. Each port is idiomatic in its own language, but the wire shape — level names, correlationId, http.request/response, user, telemetry, error serialization — is shared, so logs from a TypeScript Lambda, a Go worker, a Python API, a Rust service, and a .NET job land in the same queries.

  • TypeScript (src/) — the original. AWS server logging plus the only browser logger (device/browser detection, fetch correlation).
  • Python (python/) — full port, plus Socket.IO and Uvicorn logging adapters.
  • Rust (rust/logger/) — serde-based port; Lambda context helpers behind an aws-lambda feature flag.
  • Go (go/) — full port on log/slog, including Lambda/SQS helpers and OTel span correlation.
  • .NET (dotnet/) — full port; integrates with Microsoft.Extensions.Logging, trace correlation via System.Diagnostics.Activity.

The ports are not all identical — the honest capability matrix is below.


Feature tour

CapabilityWhere
🔗Correlation across servicesAll 5 languages
AWS context, captured automaticallyAll 5 languages
🔭Logs that join your tracesTS · Python · Rust · Go (+ .NET via Activity)
📍Exact caller locationAll 5 languages
🎨Pretty local output + rotating file logsAll 5 languages
🕶️Sensitive-key redactionAll 5 languages
🖥️Browser loggingTypeScript only

🔗 Correlation across services

A correlation ID set (or extracted from an incoming header, Lambda event, or SQS record) follows the request everywhere, in every language:

// Service A: API Gateway handler (TypeScript)logger.addLambdaContext(event,context);logger.info("Request received");// correlationId: abc-123// Service B: SQS processor (extracts the ID from the record)logger.addSQSRecordContext(record);logger.info("Processing message");// same correlationId: abc-123
// Service C: a Go worker — same schema, same IDl.AddHTTPRequest(logger.HTTPRequest{
Headers: map[string]string{"X-Correlation-Id": "abc-123"},
})
l.Info("Completing workflow", logger.Map{"orderId": "ord_1"}) // still abc-123
%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
B["Browser<br/>BrowserLogger (TS)"] -->|"X-Correlation-Id: abc-123"| GW["API Gateway → Lambda<br/>AwsServerLogger (TS)"]
GW -->|"SQS message attributes"| Q["SQS worker<br/>(Go port)"]
Q -->|"HTTP header"| API["Internal API<br/>(Python / .NET / Rust port)"]
GW -.-> LOGS[("One query:<br/>correlationId = abc-123")]
Q -.-> LOGS
API -.-> LOGS
B -.-> LOGS
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class LOGS warm
class B teal
Loading

⚡ AWS context, captured automatically

Hand the logger the Lambda event/context (or SQS record, or HTTP request) once; every subsequent line carries the invocation metadata — function, region, request IDs, HTTP method/path/headers, SQS attributes.

import{AwsServerLogger}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "UserAPI"});exportconsthandler=async(event,context)=>{logger.addLambdaContext(event,context);try{constuser=awaitcreateUser(event.body);logger.info("User created successfully",{userId: user.id});return{statusCode: 201,body: JSON.stringify(user)};}catch(error){logger.error("Failed to create user",error,{body: event.body});throwerror;}};

The same helpers exist in each port: add_lambda_context (Python), NewLambdaLogger / AddSQSRecordContext (Go), AddLambdaContext (.NET), and Lambda environment/event helpers behind the aws-lambda feature (Rust).

🔭 Logs that join your traces

Historically every line's traceId was a fabricated UUID — useless for joining logs to traces. Now, when an OpenTelemetry span is active, TypeScript, Python, Rust, and Go stamp the span's real W3C trace_id and span_id onto the line, matching what your tracing backend recorded. No active span → the UUID fallback, unchanged.

// Go: thread the context and the active span's IDs land on the linel.InfoContext(ctx, "Order shipped", logger.Map{"orderId": "ord_1"})
// → { "traceId": "4bf92f35…", "spanId": "00f067aa…", … }

Each port depends only on the OTel API (no SDK, no exporter). .NET is the one exception: it takes no OpenTelemetry dependency at all and reads the same real W3C IDs from System.Diagnostics.Activity.Current — the API OTel .NET itself builds on — so the output is equivalent.

📍 Exact caller location

Every entry includes where in the code it was emitted, in all five languages:

{
"callerContext": {
"stack": [
"at UserService.createUser (/src/services/UserService.ts:42:16)",
"at processRequest (/src/handlers/userHandler.ts:15:23)",
],
},
}

Two shapes are in play, and the difference is deliberate:

shapeportshow
callerContext.stack — multiple framesTypeScript, Pythonwalks the runtime stack
caller: { file, line, function } — one frameGo, Rust, .NETzero-cost compile-time / runtime.Caller
{ "caller": { "file": "UserService.cs", "line": 42, "function": "CreateUser" } }

Rust omits function: #[track_caller] gives file and line for free, but std::panic::Location carries no symbol name and resolving one would mean capturing a backtrace on every line. .NET uses [CallerFilePath]/[CallerLineNumber]/[CallerMemberName], which the compiler fills in at each call site — no StackTrace walk. Both emit the file basename only; the full path is build-machine noise.

🎨 Pretty local output + rotating file logs

All five ports detect local development and switch from strict JSON lines to ANSI pretty-printing — and write logs to disk under .smooai-logs/ with size/interval-based rotation:

constlogger=newAwsServerLogger({prettyPrint: true,// auto-enabled locallyrotation: {size: "10M",interval: "1d",compress: true},});

🕶️ Sensitive-key redaction

Every port scrubs values whose keys match a redaction list (case-insensitive, recursive) before a line is emitted — password, token, authorization, and friends by default, extensible per logger (addRedactKeys / add_redact_keys / DefaultRedactKeys…).

🖥️ Browser logging

TypeScript only.BrowserLogger captures device type, browser name/version, platform, and user agent, and correlates fetches to your backend logs:

import{BrowserLogger}from"@smooai/logger/browser/BrowserLogger";constlogger=newBrowserLogger({name: "CheckoutFlow"});constresponse=awaitfetch("/api/checkout",{method: "POST",headers: {"X-Correlation-Id": logger.correlationId()},});logger.addResponseContext(response);logger.info("Checkout completed",{orderId: data.id});

📦 Install

LanguagePackageInstall
TypeScript@smooai/loggerpnpm add @smooai/logger
Pythonsmooai-loggerpip install smooai-logger (or uv add smooai-logger)
Rustsmooai-loggercargo add smooai-logger
Gogithub.com/SmooAI/logger/go/v4go get github.com/SmooAI/logger/go/v4
.NETSmooAI.Loggerdotnet add package SmooAI.Logger

🚀 Quickstart

TypeScript (the original port — see AWS context and browser above for fuller examples):

// AWS environments (Lambda, ECS, EC2, …)import{AwsServerLogger,Level}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "OrderService",level: Level.Info});logger.addUserContext({id: "user-123",role: "admin"});// persists across logslogger.addTelemetryFields({duration: 150,operation: "db-query"});logger.info("Payment processed",{amount: 99.99,currency: "USD"});try{awaitriskyOperation();}catch(error){logger.error("Operation failed",error,{context: "additional-info"});// → error message, stack trace, error type, and your context}

Six levels in every port — TRACE · DEBUG · INFO · WARN · ERROR · FATAL — plus context presets (MINIMAL / FULL).

Per-language quickstarts, with full API docs:


One schema, five ports

The wire schema is shared; port depth is not identical. Here's the honest status of each surface:

CapabilityTypeScriptPythonRustGo.NET
Structured JSON, 6 levels
Correlation / request / trace IDs
HTTP request/response context
User context + telemetry fields
Lambda / SQS / API Gateway helpers✅ ¹
Pretty local output
Rotating file logs (.smooai-logs/)
Sensitive-key redaction
OTel span → traceId/spanId stamping➖ ²
Per-line caller location ⁴
Browser logger
Parity corpus enforced in tests ³

¹ Behind the aws-lambda cargo feature; Lambda environment context needs no feature. ² No OTel dependency — equivalent real W3C trace/span IDs read from System.Diagnostics.Activity.Current, which is the API OpenTelemetry .NET itself builds on. Log lines also tee upstream via SmooLoggerOptions.ForwardTo (an ILogger), the hook OTel's .NET log appender attaches to. ³ parity-corpus.json is the cross-language output contract, and all five ports now replay it from that one committed file — TypeScript (src/parity-corpus.spec.ts), Python (python/tests/test_parity_corpus.py), Rust (rust/logger/tests/parity_corpus.rs), Go (go/parity_corpus_test.go), and .NET (dotnet/tests/SmooAI.Logger.Tests/ParityCorpusTests.cs). It covers level mapping, required field names, message shape, correlation-id propagation, and the default redaction key list. Editing a corpus value turns all five suites red. ⁴ Two shapes: TypeScript and Python emit a multi-frame callerContext.stack; Go, Rust and .NET emit a single-frame caller object. Rust's omits function — see Exact caller location.

CI does cover all five languages on every PR (pr-checks.yml typechecks, lints, tests, and builds TS, Python, Rust, Go, and .NET), and release.yml publishes all five: npm → PyPI → crates.io → Go module tag → NuGet.


🔎 Looking for the desktop Log Viewer?

It moved. The Rust/egui log viewer that used to live here has been rebuilt as SmooAI Observability Studio — a Dioxus native desktop client for SmooAI logs, errors, and metrics — and lives in the SmooAI/observability repo (desktop/), with builds on its releases page. The crate has been deleted from this repo; see log-viewer/DEPRECATED.md for the migration story.

🧩 Part of Smoo AI

@smooai/logger is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

Use them in your stack, or take them as a reference for how we build.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases — add one with pnpm changeset, then open a pull request referencing any related issues.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Multi-language structured logging (TypeScript, Python, Rust, Go) for AWS Lambda and browser — correlation tracking, automatic context gathering, and a Rust/egui desktop log viewer.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@smooai/logger — Contextual logging for AWS and the browser

npmPyPIcrates.ioNuGet

Smoo AIlicense

PR Checks (all 5 languages)Release

TypeScriptPythonRustGo.NET

What it is · Feature tour · Install · Quickstart · Language matrix · Platform


A log line that only carries the message is a clue. One that carries the whole story is an answer.@smooai/logger stamps every entry with the request journey (correlation IDs across services), the AWS runtime around it (Lambda, SQS, API Gateway context), and — where an OpenTelemetry span is active — the real W3C trace and span IDs, so logs join your traces instead of floating beside them. Native ports in five languages — TypeScript, Python, Rust, Go, and .NET — emit the same JSON shape, so a request crossing language boundaries still reads as one story.

Traditional loggers give you the message, but not the story. @smooai/logger records where the log came from, the request journey that led there, and the runtime around it — so a production failure reads like a trace, not a guess.


What is this?

One structured-logging schema, implemented natively five times. Each port is idiomatic in its own language, but the wire shape — level names, correlationId, http.request/response, user, telemetry, error serialization — is shared, so logs from a TypeScript Lambda, a Go worker, a Python API, a Rust service, and a .NET job land in the same queries.

  • TypeScript (src/) — the original. AWS server logging plus the only browser logger (device/browser detection, fetch correlation).
  • Python (python/) — full port, plus Socket.IO and Uvicorn logging adapters.
  • Rust (rust/logger/) — serde-based port; Lambda context helpers behind an aws-lambda feature flag.
  • Go (go/) — full port on log/slog, including Lambda/SQS helpers and OTel span correlation.
  • .NET (dotnet/) — full port; integrates with Microsoft.Extensions.Logging, trace correlation via System.Diagnostics.Activity.

The ports are not all identical — the honest capability matrix is below.


Feature tour

CapabilityWhere
🔗Correlation across servicesAll 5 languages
AWS context, captured automaticallyAll 5 languages
🔭Logs that join your tracesTS · Python · Rust · Go (+ .NET via Activity)
📍Exact caller locationAll 5 languages
🎨Pretty local output + rotating file logsAll 5 languages
🕶️Sensitive-key redactionAll 5 languages
🖥️Browser loggingTypeScript only

🔗 Correlation across services

A correlation ID set (or extracted from an incoming header, Lambda event, or SQS record) follows the request everywhere, in every language:

// Service A: API Gateway handler (TypeScript)logger.addLambdaContext(event,context);logger.info("Request received");// correlationId: abc-123// Service B: SQS processor (extracts the ID from the record)logger.addSQSRecordContext(record);logger.info("Processing message");// same correlationId: abc-123
// Service C: a Go worker — same schema, same IDl.AddHTTPRequest(logger.HTTPRequest{
Headers: map[string]string{"X-Correlation-Id": "abc-123"},
})
l.Info("Completing workflow", logger.Map{"orderId": "ord_1"}) // still abc-123
%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
B["Browser<br/>BrowserLogger (TS)"] -->|"X-Correlation-Id: abc-123"| GW["API Gateway → Lambda<br/>AwsServerLogger (TS)"]
GW -->|"SQS message attributes"| Q["SQS worker<br/>(Go port)"]
Q -->|"HTTP header"| API["Internal API<br/>(Python / .NET / Rust port)"]
GW -.-> LOGS[("One query:<br/>correlationId = abc-123")]
Q -.-> LOGS
API -.-> LOGS
B -.-> LOGS
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class LOGS warm
class B teal
Loading

⚡ AWS context, captured automatically

Hand the logger the Lambda event/context (or SQS record, or HTTP request) once; every subsequent line carries the invocation metadata — function, region, request IDs, HTTP method/path/headers, SQS attributes.

import{AwsServerLogger}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "UserAPI"});exportconsthandler=async(event,context)=>{logger.addLambdaContext(event,context);try{constuser=awaitcreateUser(event.body);logger.info("User created successfully",{userId: user.id});return{statusCode: 201,body: JSON.stringify(user)};}catch(error){logger.error("Failed to create user",error,{body: event.body});throwerror;}};

The same helpers exist in each port: add_lambda_context (Python), NewLambdaLogger / AddSQSRecordContext (Go), AddLambdaContext (.NET), and Lambda environment/event helpers behind the aws-lambda feature (Rust).

🔭 Logs that join your traces

Historically every line's traceId was a fabricated UUID — useless for joining logs to traces. Now, when an OpenTelemetry span is active, TypeScript, Python, Rust, and Go stamp the span's real W3C trace_id and span_id onto the line, matching what your tracing backend recorded. No active span → the UUID fallback, unchanged.

// Go: thread the context and the active span's IDs land on the linel.InfoContext(ctx, "Order shipped", logger.Map{"orderId": "ord_1"})
// → { "traceId": "4bf92f35…", "spanId": "00f067aa…", … }

Each port depends only on the OTel API (no SDK, no exporter). .NET is the one exception: it takes no OpenTelemetry dependency at all and reads the same real W3C IDs from System.Diagnostics.Activity.Current — the API OTel .NET itself builds on — so the output is equivalent.

📍 Exact caller location

Every entry includes where in the code it was emitted, in all five languages:

{
"callerContext": {
"stack": [
"at UserService.createUser (/src/services/UserService.ts:42:16)",
"at processRequest (/src/handlers/userHandler.ts:15:23)",
],
},
}

Two shapes are in play, and the difference is deliberate:

shapeportshow
callerContext.stack — multiple framesTypeScript, Pythonwalks the runtime stack
caller: { file, line, function } — one frameGo, Rust, .NETzero-cost compile-time / runtime.Caller
{ "caller": { "file": "UserService.cs", "line": 42, "function": "CreateUser" } }

Rust omits function: #[track_caller] gives file and line for free, but std::panic::Location carries no symbol name and resolving one would mean capturing a backtrace on every line. .NET uses [CallerFilePath]/[CallerLineNumber]/[CallerMemberName], which the compiler fills in at each call site — no StackTrace walk. Both emit the file basename only; the full path is build-machine noise.

🎨 Pretty local output + rotating file logs

All five ports detect local development and switch from strict JSON lines to ANSI pretty-printing — and write logs to disk under .smooai-logs/ with size/interval-based rotation:

constlogger=newAwsServerLogger({prettyPrint: true,// auto-enabled locallyrotation: {size: "10M",interval: "1d",compress: true},});

🕶️ Sensitive-key redaction

Every port scrubs values whose keys match a redaction list (case-insensitive, recursive) before a line is emitted — password, token, authorization, and friends by default, extensible per logger (addRedactKeys / add_redact_keys / DefaultRedactKeys…).

🖥️ Browser logging

TypeScript only.BrowserLogger captures device type, browser name/version, platform, and user agent, and correlates fetches to your backend logs:

import{BrowserLogger}from"@smooai/logger/browser/BrowserLogger";constlogger=newBrowserLogger({name: "CheckoutFlow"});constresponse=awaitfetch("/api/checkout",{method: "POST",headers: {"X-Correlation-Id": logger.correlationId()},});logger.addResponseContext(response);logger.info("Checkout completed",{orderId: data.id});

📦 Install

LanguagePackageInstall
TypeScript@smooai/loggerpnpm add @smooai/logger
Pythonsmooai-loggerpip install smooai-logger (or uv add smooai-logger)
Rustsmooai-loggercargo add smooai-logger
Gogithub.com/SmooAI/logger/go/v4go get github.com/SmooAI/logger/go/v4
.NETSmooAI.Loggerdotnet add package SmooAI.Logger

🚀 Quickstart

TypeScript (the original port — see AWS context and browser above for fuller examples):

// AWS environments (Lambda, ECS, EC2, …)import{AwsServerLogger,Level}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "OrderService",level: Level.Info});logger.addUserContext({id: "user-123",role: "admin"});// persists across logslogger.addTelemetryFields({duration: 150,operation: "db-query"});logger.info("Payment processed",{amount: 99.99,currency: "USD"});try{awaitriskyOperation();}catch(error){logger.error("Operation failed",error,{context: "additional-info"});// → error message, stack trace, error type, and your context}

Six levels in every port — TRACE · DEBUG · INFO · WARN · ERROR · FATAL — plus context presets (MINIMAL / FULL).

Per-language quickstarts, with full API docs:


One schema, five ports

The wire schema is shared; port depth is not identical. Here's the honest status of each surface:

CapabilityTypeScriptPythonRustGo.NET
Structured JSON, 6 levels
Correlation / request / trace IDs
HTTP request/response context
User context + telemetry fields
Lambda / SQS / API Gateway helpers✅ ¹
Pretty local output
Rotating file logs (.smooai-logs/)
Sensitive-key redaction
OTel span → traceId/spanId stamping➖ ²
Per-line caller location ⁴
Browser logger
Parity corpus enforced in tests ³

¹ Behind the aws-lambda cargo feature; Lambda environment context needs no feature. ² No OTel dependency — equivalent real W3C trace/span IDs read from System.Diagnostics.Activity.Current, which is the API OpenTelemetry .NET itself builds on. Log lines also tee upstream via SmooLoggerOptions.ForwardTo (an ILogger), the hook OTel's .NET log appender attaches to. ³ parity-corpus.json is the cross-language output contract, and all five ports now replay it from that one committed file — TypeScript (src/parity-corpus.spec.ts), Python (python/tests/test_parity_corpus.py), Rust (rust/logger/tests/parity_corpus.rs), Go (go/parity_corpus_test.go), and .NET (dotnet/tests/SmooAI.Logger.Tests/ParityCorpusTests.cs). It covers level mapping, required field names, message shape, correlation-id propagation, and the default redaction key list. Editing a corpus value turns all five suites red. ⁴ Two shapes: TypeScript and Python emit a multi-frame callerContext.stack; Go, Rust and .NET emit a single-frame caller object. Rust's omits function — see Exact caller location.

CI does cover all five languages on every PR (pr-checks.yml typechecks, lints, tests, and builds TS, Python, Rust, Go, and .NET), and release.yml publishes all five: npm → PyPI → crates.io → Go module tag → NuGet.


🔎 Looking for the desktop Log Viewer?

It moved. The Rust/egui log viewer that used to live here has been rebuilt as SmooAI Observability Studio — a Dioxus native desktop client for SmooAI logs, errors, and metrics — and lives in the SmooAI/observability repo (desktop/), with builds on its releases page. The crate has been deleted from this repo; see log-viewer/DEPRECATED.md for the migration story.

🧩 Part of Smoo AI

@smooai/logger is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

Use them in your stack, or take them as a reference for how we build.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases — add one with pnpm changeset, then open a pull request referencing any related issues.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Multi-language structured logging (TypeScript, Python, Rust, Go) for AWS Lambda and browser — correlation tracking, automatic context gathering, and a Rust/egui desktop log viewer.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@smooai/logger — Contextual logging for AWS and the browser

npmPyPIcrates.ioNuGet

Smoo AIlicense

PR Checks (all 5 languages)Release

TypeScriptPythonRustGo.NET

What it is · Feature tour · Install · Quickstart · Language matrix · Platform


A log line that only carries the message is a clue. One that carries the whole story is an answer.@smooai/logger stamps every entry with the request journey (correlation IDs across services), the AWS runtime around it (Lambda, SQS, API Gateway context), and — where an OpenTelemetry span is active — the real W3C trace and span IDs, so logs join your traces instead of floating beside them. Native ports in five languages — TypeScript, Python, Rust, Go, and .NET — emit the same JSON shape, so a request crossing language boundaries still reads as one story.

Traditional loggers give you the message, but not the story. @smooai/logger records where the log came from, the request journey that led there, and the runtime around it — so a production failure reads like a trace, not a guess.


What is this?

One structured-logging schema, implemented natively five times. Each port is idiomatic in its own language, but the wire shape — level names, correlationId, http.request/response, user, telemetry, error serialization — is shared, so logs from a TypeScript Lambda, a Go worker, a Python API, a Rust service, and a .NET job land in the same queries.

  • TypeScript (src/) — the original. AWS server logging plus the only browser logger (device/browser detection, fetch correlation).
  • Python (python/) — full port, plus Socket.IO and Uvicorn logging adapters.
  • Rust (rust/logger/) — serde-based port; Lambda context helpers behind an aws-lambda feature flag.
  • Go (go/) — full port on log/slog, including Lambda/SQS helpers and OTel span correlation.
  • .NET (dotnet/) — full port; integrates with Microsoft.Extensions.Logging, trace correlation via System.Diagnostics.Activity.

The ports are not all identical — the honest capability matrix is below.


Feature tour

CapabilityWhere
🔗Correlation across servicesAll 5 languages
AWS context, captured automaticallyAll 5 languages
🔭Logs that join your tracesTS · Python · Rust · Go (+ .NET via Activity)
📍Exact caller locationAll 5 languages
🎨Pretty local output + rotating file logsAll 5 languages
🕶️Sensitive-key redactionAll 5 languages
🖥️Browser loggingTypeScript only

🔗 Correlation across services

A correlation ID set (or extracted from an incoming header, Lambda event, or SQS record) follows the request everywhere, in every language:

// Service A: API Gateway handler (TypeScript)logger.addLambdaContext(event,context);logger.info("Request received");// correlationId: abc-123// Service B: SQS processor (extracts the ID from the record)logger.addSQSRecordContext(record);logger.info("Processing message");// same correlationId: abc-123
// Service C: a Go worker — same schema, same IDl.AddHTTPRequest(logger.HTTPRequest{
Headers: map[string]string{"X-Correlation-Id": "abc-123"},
})
l.Info("Completing workflow", logger.Map{"orderId": "ord_1"}) // still abc-123
%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
B["Browser<br/>BrowserLogger (TS)"] -->|"X-Correlation-Id: abc-123"| GW["API Gateway → Lambda<br/>AwsServerLogger (TS)"]
GW -->|"SQS message attributes"| Q["SQS worker<br/>(Go port)"]
Q -->|"HTTP header"| API["Internal API<br/>(Python / .NET / Rust port)"]
GW -.-> LOGS[("One query:<br/>correlationId = abc-123")]
Q -.-> LOGS
API -.-> LOGS
B -.-> LOGS
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class LOGS warm
class B teal
Loading

⚡ AWS context, captured automatically

Hand the logger the Lambda event/context (or SQS record, or HTTP request) once; every subsequent line carries the invocation metadata — function, region, request IDs, HTTP method/path/headers, SQS attributes.

import{AwsServerLogger}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "UserAPI"});exportconsthandler=async(event,context)=>{logger.addLambdaContext(event,context);try{constuser=awaitcreateUser(event.body);logger.info("User created successfully",{userId: user.id});return{statusCode: 201,body: JSON.stringify(user)};}catch(error){logger.error("Failed to create user",error,{body: event.body});throwerror;}};

The same helpers exist in each port: add_lambda_context (Python), NewLambdaLogger / AddSQSRecordContext (Go), AddLambdaContext (.NET), and Lambda environment/event helpers behind the aws-lambda feature (Rust).

🔭 Logs that join your traces

Historically every line's traceId was a fabricated UUID — useless for joining logs to traces. Now, when an OpenTelemetry span is active, TypeScript, Python, Rust, and Go stamp the span's real W3C trace_id and span_id onto the line, matching what your tracing backend recorded. No active span → the UUID fallback, unchanged.

// Go: thread the context and the active span's IDs land on the linel.InfoContext(ctx, "Order shipped", logger.Map{"orderId": "ord_1"})
// → { "traceId": "4bf92f35…", "spanId": "00f067aa…", … }

Each port depends only on the OTel API (no SDK, no exporter). .NET is the one exception: it takes no OpenTelemetry dependency at all and reads the same real W3C IDs from System.Diagnostics.Activity.Current — the API OTel .NET itself builds on — so the output is equivalent.

📍 Exact caller location

Every entry includes where in the code it was emitted, in all five languages:

{
"callerContext": {
"stack": [
"at UserService.createUser (/src/services/UserService.ts:42:16)",
"at processRequest (/src/handlers/userHandler.ts:15:23)",
],
},
}

Two shapes are in play, and the difference is deliberate:

shapeportshow
callerContext.stack — multiple framesTypeScript, Pythonwalks the runtime stack
caller: { file, line, function } — one frameGo, Rust, .NETzero-cost compile-time / runtime.Caller
{ "caller": { "file": "UserService.cs", "line": 42, "function": "CreateUser" } }

Rust omits function: #[track_caller] gives file and line for free, but std::panic::Location carries no symbol name and resolving one would mean capturing a backtrace on every line. .NET uses [CallerFilePath]/[CallerLineNumber]/[CallerMemberName], which the compiler fills in at each call site — no StackTrace walk. Both emit the file basename only; the full path is build-machine noise.

🎨 Pretty local output + rotating file logs

All five ports detect local development and switch from strict JSON lines to ANSI pretty-printing — and write logs to disk under .smooai-logs/ with size/interval-based rotation:

constlogger=newAwsServerLogger({prettyPrint: true,// auto-enabled locallyrotation: {size: "10M",interval: "1d",compress: true},});

🕶️ Sensitive-key redaction

Every port scrubs values whose keys match a redaction list (case-insensitive, recursive) before a line is emitted — password, token, authorization, and friends by default, extensible per logger (addRedactKeys / add_redact_keys / DefaultRedactKeys…).

🖥️ Browser logging

TypeScript only.BrowserLogger captures device type, browser name/version, platform, and user agent, and correlates fetches to your backend logs:

import{BrowserLogger}from"@smooai/logger/browser/BrowserLogger";constlogger=newBrowserLogger({name: "CheckoutFlow"});constresponse=awaitfetch("/api/checkout",{method: "POST",headers: {"X-Correlation-Id": logger.correlationId()},});logger.addResponseContext(response);logger.info("Checkout completed",{orderId: data.id});

📦 Install

LanguagePackageInstall
TypeScript@smooai/loggerpnpm add @smooai/logger
Pythonsmooai-loggerpip install smooai-logger (or uv add smooai-logger)
Rustsmooai-loggercargo add smooai-logger
Gogithub.com/SmooAI/logger/go/v4go get github.com/SmooAI/logger/go/v4
.NETSmooAI.Loggerdotnet add package SmooAI.Logger

🚀 Quickstart

TypeScript (the original port — see AWS context and browser above for fuller examples):

// AWS environments (Lambda, ECS, EC2, …)import{AwsServerLogger,Level}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "OrderService",level: Level.Info});logger.addUserContext({id: "user-123",role: "admin"});// persists across logslogger.addTelemetryFields({duration: 150,operation: "db-query"});logger.info("Payment processed",{amount: 99.99,currency: "USD"});try{awaitriskyOperation();}catch(error){logger.error("Operation failed",error,{context: "additional-info"});// → error message, stack trace, error type, and your context}

Six levels in every port — TRACE · DEBUG · INFO · WARN · ERROR · FATAL — plus context presets (MINIMAL / FULL).

Per-language quickstarts, with full API docs:


One schema, five ports

The wire schema is shared; port depth is not identical. Here's the honest status of each surface:

CapabilityTypeScriptPythonRustGo.NET
Structured JSON, 6 levels
Correlation / request / trace IDs
HTTP request/response context
User context + telemetry fields
Lambda / SQS / API Gateway helpers✅ ¹
Pretty local output
Rotating file logs (.smooai-logs/)
Sensitive-key redaction
OTel span → traceId/spanId stamping➖ ²
Per-line caller location ⁴
Browser logger
Parity corpus enforced in tests ³

¹ Behind the aws-lambda cargo feature; Lambda environment context needs no feature. ² No OTel dependency — equivalent real W3C trace/span IDs read from System.Diagnostics.Activity.Current, which is the API OpenTelemetry .NET itself builds on. Log lines also tee upstream via SmooLoggerOptions.ForwardTo (an ILogger), the hook OTel's .NET log appender attaches to. ³ parity-corpus.json is the cross-language output contract, and all five ports now replay it from that one committed file — TypeScript (src/parity-corpus.spec.ts), Python (python/tests/test_parity_corpus.py), Rust (rust/logger/tests/parity_corpus.rs), Go (go/parity_corpus_test.go), and .NET (dotnet/tests/SmooAI.Logger.Tests/ParityCorpusTests.cs). It covers level mapping, required field names, message shape, correlation-id propagation, and the default redaction key list. Editing a corpus value turns all five suites red. ⁴ Two shapes: TypeScript and Python emit a multi-frame callerContext.stack; Go, Rust and .NET emit a single-frame caller object. Rust's omits function — see Exact caller location.

CI does cover all five languages on every PR (pr-checks.yml typechecks, lints, tests, and builds TS, Python, Rust, Go, and .NET), and release.yml publishes all five: npm → PyPI → crates.io → Go module tag → NuGet.


🔎 Looking for the desktop Log Viewer?

It moved. The Rust/egui log viewer that used to live here has been rebuilt as SmooAI Observability Studio — a Dioxus native desktop client for SmooAI logs, errors, and metrics — and lives in the SmooAI/observability repo (desktop/), with builds on its releases page. The crate has been deleted from this repo; see log-viewer/DEPRECATED.md for the migration story.

🧩 Part of Smoo AI

@smooai/logger is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

Use them in your stack, or take them as a reference for how we build.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases — add one with pnpm changeset, then open a pull request referencing any related issues.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Multi-language structured logging (TypeScript, Python, Rust, Go) for AWS Lambda and browser — correlation tracking, automatic context gathering, and a Rust/egui desktop log viewer.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

@smooai/logger — Contextual logging for AWS and the browser

npmPyPIcrates.ioNuGet

Smoo AIlicense

PR Checks (all 5 languages)Release

TypeScriptPythonRustGo.NET

What it is · Feature tour · Install · Quickstart · Language matrix · Platform


A log line that only carries the message is a clue. One that carries the whole story is an answer.@smooai/logger stamps every entry with the request journey (correlation IDs across services), the AWS runtime around it (Lambda, SQS, API Gateway context), and — where an OpenTelemetry span is active — the real W3C trace and span IDs, so logs join your traces instead of floating beside them. Native ports in five languages — TypeScript, Python, Rust, Go, and .NET — emit the same JSON shape, so a request crossing language boundaries still reads as one story.

Traditional loggers give you the message, but not the story. @smooai/logger records where the log came from, the request journey that led there, and the runtime around it — so a production failure reads like a trace, not a guess.


What is this?

One structured-logging schema, implemented natively five times. Each port is idiomatic in its own language, but the wire shape — level names, correlationId, http.request/response, user, telemetry, error serialization — is shared, so logs from a TypeScript Lambda, a Go worker, a Python API, a Rust service, and a .NET job land in the same queries.

  • TypeScript (src/) — the original. AWS server logging plus the only browser logger (device/browser detection, fetch correlation).
  • Python (python/) — full port, plus Socket.IO and Uvicorn logging adapters.
  • Rust (rust/logger/) — serde-based port; Lambda context helpers behind an aws-lambda feature flag.
  • Go (go/) — full port on log/slog, including Lambda/SQS helpers and OTel span correlation.
  • .NET (dotnet/) — full port; integrates with Microsoft.Extensions.Logging, trace correlation via System.Diagnostics.Activity.

The ports are not all identical — the honest capability matrix is below.


Feature tour

CapabilityWhere
🔗Correlation across servicesAll 5 languages
AWS context, captured automaticallyAll 5 languages
🔭Logs that join your tracesTS · Python · Rust · Go (+ .NET via Activity)
📍Exact caller locationAll 5 languages
🎨Pretty local output + rotating file logsAll 5 languages
🕶️Sensitive-key redactionAll 5 languages
🖥️Browser loggingTypeScript only

🔗 Correlation across services

A correlation ID set (or extracted from an incoming header, Lambda event, or SQS record) follows the request everywhere, in every language:

// Service A: API Gateway handler (TypeScript)logger.addLambdaContext(event,context);logger.info("Request received");// correlationId: abc-123// Service B: SQS processor (extracts the ID from the record)logger.addSQSRecordContext(record);logger.info("Processing message");// same correlationId: abc-123
// Service C: a Go worker — same schema, same IDl.AddHTTPRequest(logger.HTTPRequest{
Headers: map[string]string{"X-Correlation-Id": "abc-123"},
})
l.Info("Completing workflow", logger.Map{"orderId": "ord_1"}) // still abc-123
%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
B["Browser<br/>BrowserLogger (TS)"] -->|"X-Correlation-Id: abc-123"| GW["API Gateway → Lambda<br/>AwsServerLogger (TS)"]
GW -->|"SQS message attributes"| Q["SQS worker<br/>(Go port)"]
Q -->|"HTTP header"| API["Internal API<br/>(Python / .NET / Rust port)"]
GW -.-> LOGS[("One query:<br/>correlationId = abc-123")]
Q -.-> LOGS
API -.-> LOGS
B -.-> LOGS
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class LOGS warm
class B teal
Loading

⚡ AWS context, captured automatically

Hand the logger the Lambda event/context (or SQS record, or HTTP request) once; every subsequent line carries the invocation metadata — function, region, request IDs, HTTP method/path/headers, SQS attributes.

import{AwsServerLogger}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "UserAPI"});exportconsthandler=async(event,context)=>{logger.addLambdaContext(event,context);try{constuser=awaitcreateUser(event.body);logger.info("User created successfully",{userId: user.id});return{statusCode: 201,body: JSON.stringify(user)};}catch(error){logger.error("Failed to create user",error,{body: event.body});throwerror;}};

The same helpers exist in each port: add_lambda_context (Python), NewLambdaLogger / AddSQSRecordContext (Go), AddLambdaContext (.NET), and Lambda environment/event helpers behind the aws-lambda feature (Rust).

🔭 Logs that join your traces

Historically every line's traceId was a fabricated UUID — useless for joining logs to traces. Now, when an OpenTelemetry span is active, TypeScript, Python, Rust, and Go stamp the span's real W3C trace_id and span_id onto the line, matching what your tracing backend recorded. No active span → the UUID fallback, unchanged.

// Go: thread the context and the active span's IDs land on the linel.InfoContext(ctx, "Order shipped", logger.Map{"orderId": "ord_1"})
// → { "traceId": "4bf92f35…", "spanId": "00f067aa…", … }

Each port depends only on the OTel API (no SDK, no exporter). .NET is the one exception: it takes no OpenTelemetry dependency at all and reads the same real W3C IDs from System.Diagnostics.Activity.Current — the API OTel .NET itself builds on — so the output is equivalent.

📍 Exact caller location

Every entry includes where in the code it was emitted, in all five languages:

{
"callerContext": {
"stack": [
"at UserService.createUser (/src/services/UserService.ts:42:16)",
"at processRequest (/src/handlers/userHandler.ts:15:23)",
],
},
}

Two shapes are in play, and the difference is deliberate:

shapeportshow
callerContext.stack — multiple framesTypeScript, Pythonwalks the runtime stack
caller: { file, line, function } — one frameGo, Rust, .NETzero-cost compile-time / runtime.Caller
{ "caller": { "file": "UserService.cs", "line": 42, "function": "CreateUser" } }

Rust omits function: #[track_caller] gives file and line for free, but std::panic::Location carries no symbol name and resolving one would mean capturing a backtrace on every line. .NET uses [CallerFilePath]/[CallerLineNumber]/[CallerMemberName], which the compiler fills in at each call site — no StackTrace walk. Both emit the file basename only; the full path is build-machine noise.

🎨 Pretty local output + rotating file logs

All five ports detect local development and switch from strict JSON lines to ANSI pretty-printing — and write logs to disk under .smooai-logs/ with size/interval-based rotation:

constlogger=newAwsServerLogger({prettyPrint: true,// auto-enabled locallyrotation: {size: "10M",interval: "1d",compress: true},});

🕶️ Sensitive-key redaction

Every port scrubs values whose keys match a redaction list (case-insensitive, recursive) before a line is emitted — password, token, authorization, and friends by default, extensible per logger (addRedactKeys / add_redact_keys / DefaultRedactKeys…).

🖥️ Browser logging

TypeScript only.BrowserLogger captures device type, browser name/version, platform, and user agent, and correlates fetches to your backend logs:

import{BrowserLogger}from"@smooai/logger/browser/BrowserLogger";constlogger=newBrowserLogger({name: "CheckoutFlow"});constresponse=awaitfetch("/api/checkout",{method: "POST",headers: {"X-Correlation-Id": logger.correlationId()},});logger.addResponseContext(response);logger.info("Checkout completed",{orderId: data.id});

📦 Install

LanguagePackageInstall
TypeScript@smooai/loggerpnpm add @smooai/logger
Pythonsmooai-loggerpip install smooai-logger (or uv add smooai-logger)
Rustsmooai-loggercargo add smooai-logger
Gogithub.com/SmooAI/logger/go/v4go get github.com/SmooAI/logger/go/v4
.NETSmooAI.Loggerdotnet add package SmooAI.Logger

🚀 Quickstart

TypeScript (the original port — see AWS context and browser above for fuller examples):

// AWS environments (Lambda, ECS, EC2, …)import{AwsServerLogger,Level}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "OrderService",level: Level.Info});logger.addUserContext({id: "user-123",role: "admin"});// persists across logslogger.addTelemetryFields({duration: 150,operation: "db-query"});logger.info("Payment processed",{amount: 99.99,currency: "USD"});try{awaitriskyOperation();}catch(error){logger.error("Operation failed",error,{context: "additional-info"});// → error message, stack trace, error type, and your context}

Six levels in every port — TRACE · DEBUG · INFO · WARN · ERROR · FATAL — plus context presets (MINIMAL / FULL).

Per-language quickstarts, with full API docs:


One schema, five ports

The wire schema is shared; port depth is not identical. Here's the honest status of each surface:

CapabilityTypeScriptPythonRustGo.NET
Structured JSON, 6 levels
Correlation / request / trace IDs
HTTP request/response context
User context + telemetry fields
Lambda / SQS / API Gateway helpers✅ ¹
Pretty local output
Rotating file logs (.smooai-logs/)
Sensitive-key redaction
OTel span → traceId/spanId stamping➖ ²
Per-line caller location ⁴
Browser logger
Parity corpus enforced in tests ³

¹ Behind the aws-lambda cargo feature; Lambda environment context needs no feature. ² No OTel dependency — equivalent real W3C trace/span IDs read from System.Diagnostics.Activity.Current, which is the API OpenTelemetry .NET itself builds on. Log lines also tee upstream via SmooLoggerOptions.ForwardTo (an ILogger), the hook OTel's .NET log appender attaches to. ³ parity-corpus.json is the cross-language output contract, and all five ports now replay it from that one committed file — TypeScript (src/parity-corpus.spec.ts), Python (python/tests/test_parity_corpus.py), Rust (rust/logger/tests/parity_corpus.rs), Go (go/parity_corpus_test.go), and .NET (dotnet/tests/SmooAI.Logger.Tests/ParityCorpusTests.cs). It covers level mapping, required field names, message shape, correlation-id propagation, and the default redaction key list. Editing a corpus value turns all five suites red. ⁴ Two shapes: TypeScript and Python emit a multi-frame callerContext.stack; Go, Rust and .NET emit a single-frame caller object. Rust's omits function — see Exact caller location.

CI does cover all five languages on every PR (pr-checks.yml typechecks, lints, tests, and builds TS, Python, Rust, Go, and .NET), and release.yml publishes all five: npm → PyPI → crates.io → Go module tag → NuGet.


🔎 Looking for the desktop Log Viewer?

It moved. The Rust/egui log viewer that used to live here has been rebuilt as SmooAI Observability Studio — a Dioxus native desktop client for SmooAI logs, errors, and metrics — and lives in the SmooAI/observability repo (desktop/), with builds on its releases page. The crate has been deleted from this repo; see log-viewer/DEPRECATED.md for the migration story.

🧩 Part of Smoo AI

@smooai/logger is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

Use them in your stack, or take them as a reference for how we build.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases — add one with pnpm changeset, then open a pull request referencing any related issues.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Multi-language structured logging (TypeScript, Python, Rust, Go) for AWS Lambda and browser — correlation tracking, automatic context gathering, and a Rust/egui desktop log viewer.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@smooai/logger — Contextual logging for AWS and the browser

npmPyPIcrates.ioNuGet

Smoo AIlicense

PR Checks (all 5 languages)Release

TypeScriptPythonRustGo.NET

What it is · Feature tour · Install · Quickstart · Language matrix · Platform


A log line that only carries the message is a clue. One that carries the whole story is an answer.@smooai/logger stamps every entry with the request journey (correlation IDs across services), the AWS runtime around it (Lambda, SQS, API Gateway context), and — where an OpenTelemetry span is active — the real W3C trace and span IDs, so logs join your traces instead of floating beside them. Native ports in five languages — TypeScript, Python, Rust, Go, and .NET — emit the same JSON shape, so a request crossing language boundaries still reads as one story.

Traditional loggers give you the message, but not the story. @smooai/logger records where the log came from, the request journey that led there, and the runtime around it — so a production failure reads like a trace, not a guess.


What is this?

One structured-logging schema, implemented natively five times. Each port is idiomatic in its own language, but the wire shape — level names, correlationId, http.request/response, user, telemetry, error serialization — is shared, so logs from a TypeScript Lambda, a Go worker, a Python API, a Rust service, and a .NET job land in the same queries.

  • TypeScript (src/) — the original. AWS server logging plus the only browser logger (device/browser detection, fetch correlation).
  • Python (python/) — full port, plus Socket.IO and Uvicorn logging adapters.
  • Rust (rust/logger/) — serde-based port; Lambda context helpers behind an aws-lambda feature flag.
  • Go (go/) — full port on log/slog, including Lambda/SQS helpers and OTel span correlation.
  • .NET (dotnet/) — full port; integrates with Microsoft.Extensions.Logging, trace correlation via System.Diagnostics.Activity.

The ports are not all identical — the honest capability matrix is below.


Feature tour

CapabilityWhere
🔗Correlation across servicesAll 5 languages
AWS context, captured automaticallyAll 5 languages
🔭Logs that join your tracesTS · Python · Rust · Go (+ .NET via Activity)
📍Exact caller locationAll 5 languages
🎨Pretty local output + rotating file logsAll 5 languages
🕶️Sensitive-key redactionAll 5 languages
🖥️Browser loggingTypeScript only

🔗 Correlation across services

A correlation ID set (or extracted from an incoming header, Lambda event, or SQS record) follows the request everywhere, in every language:

// Service A: API Gateway handler (TypeScript)logger.addLambdaContext(event,context);logger.info("Request received");// correlationId: abc-123// Service B: SQS processor (extracts the ID from the record)logger.addSQSRecordContext(record);logger.info("Processing message");// same correlationId: abc-123
// Service C: a Go worker — same schema, same IDl.AddHTTPRequest(logger.HTTPRequest{
Headers: map[string]string{"X-Correlation-Id": "abc-123"},
})
l.Info("Completing workflow", logger.Map{"orderId": "ord_1"}) // still abc-123
%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
B["Browser<br/>BrowserLogger (TS)"] -->|"X-Correlation-Id: abc-123"| GW["API Gateway → Lambda<br/>AwsServerLogger (TS)"]
GW -->|"SQS message attributes"| Q["SQS worker<br/>(Go port)"]
Q -->|"HTTP header"| API["Internal API<br/>(Python / .NET / Rust port)"]
GW -.-> LOGS[("One query:<br/>correlationId = abc-123")]
Q -.-> LOGS
API -.-> LOGS
B -.-> LOGS
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class LOGS warm
class B teal
Loading

⚡ AWS context, captured automatically

Hand the logger the Lambda event/context (or SQS record, or HTTP request) once; every subsequent line carries the invocation metadata — function, region, request IDs, HTTP method/path/headers, SQS attributes.

import{AwsServerLogger}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "UserAPI"});exportconsthandler=async(event,context)=>{logger.addLambdaContext(event,context);try{constuser=awaitcreateUser(event.body);logger.info("User created successfully",{userId: user.id});return{statusCode: 201,body: JSON.stringify(user)};}catch(error){logger.error("Failed to create user",error,{body: event.body});throwerror;}};

The same helpers exist in each port: add_lambda_context (Python), NewLambdaLogger / AddSQSRecordContext (Go), AddLambdaContext (.NET), and Lambda environment/event helpers behind the aws-lambda feature (Rust).

🔭 Logs that join your traces

Historically every line's traceId was a fabricated UUID — useless for joining logs to traces. Now, when an OpenTelemetry span is active, TypeScript, Python, Rust, and Go stamp the span's real W3C trace_id and span_id onto the line, matching what your tracing backend recorded. No active span → the UUID fallback, unchanged.

// Go: thread the context and the active span's IDs land on the linel.InfoContext(ctx, "Order shipped", logger.Map{"orderId": "ord_1"})
// → { "traceId": "4bf92f35…", "spanId": "00f067aa…", … }

Each port depends only on the OTel API (no SDK, no exporter). .NET is the one exception: it takes no OpenTelemetry dependency at all and reads the same real W3C IDs from System.Diagnostics.Activity.Current — the API OTel .NET itself builds on — so the output is equivalent.

📍 Exact caller location

Every entry includes where in the code it was emitted, in all five languages:

{
"callerContext": {
"stack": [
"at UserService.createUser (/src/services/UserService.ts:42:16)",
"at processRequest (/src/handlers/userHandler.ts:15:23)",
],
},
}

Two shapes are in play, and the difference is deliberate:

shapeportshow
callerContext.stack — multiple framesTypeScript, Pythonwalks the runtime stack
caller: { file, line, function } — one frameGo, Rust, .NETzero-cost compile-time / runtime.Caller
{ "caller": { "file": "UserService.cs", "line": 42, "function": "CreateUser" } }

Rust omits function: #[track_caller] gives file and line for free, but std::panic::Location carries no symbol name and resolving one would mean capturing a backtrace on every line. .NET uses [CallerFilePath]/[CallerLineNumber]/[CallerMemberName], which the compiler fills in at each call site — no StackTrace walk. Both emit the file basename only; the full path is build-machine noise.

🎨 Pretty local output + rotating file logs

All five ports detect local development and switch from strict JSON lines to ANSI pretty-printing — and write logs to disk under .smooai-logs/ with size/interval-based rotation:

constlogger=newAwsServerLogger({prettyPrint: true,// auto-enabled locallyrotation: {size: "10M",interval: "1d",compress: true},});

🕶️ Sensitive-key redaction

Every port scrubs values whose keys match a redaction list (case-insensitive, recursive) before a line is emitted — password, token, authorization, and friends by default, extensible per logger (addRedactKeys / add_redact_keys / DefaultRedactKeys…).

🖥️ Browser logging

TypeScript only.BrowserLogger captures device type, browser name/version, platform, and user agent, and correlates fetches to your backend logs:

import{BrowserLogger}from"@smooai/logger/browser/BrowserLogger";constlogger=newBrowserLogger({name: "CheckoutFlow"});constresponse=awaitfetch("/api/checkout",{method: "POST",headers: {"X-Correlation-Id": logger.correlationId()},});logger.addResponseContext(response);logger.info("Checkout completed",{orderId: data.id});

📦 Install

LanguagePackageInstall
TypeScript@smooai/loggerpnpm add @smooai/logger
Pythonsmooai-loggerpip install smooai-logger (or uv add smooai-logger)
Rustsmooai-loggercargo add smooai-logger
Gogithub.com/SmooAI/logger/go/v4go get github.com/SmooAI/logger/go/v4
.NETSmooAI.Loggerdotnet add package SmooAI.Logger

🚀 Quickstart

TypeScript (the original port — see AWS context and browser above for fuller examples):

// AWS environments (Lambda, ECS, EC2, …)import{AwsServerLogger,Level}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "OrderService",level: Level.Info});logger.addUserContext({id: "user-123",role: "admin"});// persists across logslogger.addTelemetryFields({duration: 150,operation: "db-query"});logger.info("Payment processed",{amount: 99.99,currency: "USD"});try{awaitriskyOperation();}catch(error){logger.error("Operation failed",error,{context: "additional-info"});// → error message, stack trace, error type, and your context}

Six levels in every port — TRACE · DEBUG · INFO · WARN · ERROR · FATAL — plus context presets (MINIMAL / FULL).

Per-language quickstarts, with full API docs:


One schema, five ports

The wire schema is shared; port depth is not identical. Here's the honest status of each surface:

CapabilityTypeScriptPythonRustGo.NET
Structured JSON, 6 levels
Correlation / request / trace IDs
HTTP request/response context
User context + telemetry fields
Lambda / SQS / API Gateway helpers✅ ¹
Pretty local output
Rotating file logs (.smooai-logs/)
Sensitive-key redaction
OTel span → traceId/spanId stamping➖ ²
Per-line caller location ⁴
Browser logger
Parity corpus enforced in tests ³

¹ Behind the aws-lambda cargo feature; Lambda environment context needs no feature. ² No OTel dependency — equivalent real W3C trace/span IDs read from System.Diagnostics.Activity.Current, which is the API OpenTelemetry .NET itself builds on. Log lines also tee upstream via SmooLoggerOptions.ForwardTo (an ILogger), the hook OTel's .NET log appender attaches to. ³ parity-corpus.json is the cross-language output contract, and all five ports now replay it from that one committed file — TypeScript (src/parity-corpus.spec.ts), Python (python/tests/test_parity_corpus.py), Rust (rust/logger/tests/parity_corpus.rs), Go (go/parity_corpus_test.go), and .NET (dotnet/tests/SmooAI.Logger.Tests/ParityCorpusTests.cs). It covers level mapping, required field names, message shape, correlation-id propagation, and the default redaction key list. Editing a corpus value turns all five suites red. ⁴ Two shapes: TypeScript and Python emit a multi-frame callerContext.stack; Go, Rust and .NET emit a single-frame caller object. Rust's omits function — see Exact caller location.

CI does cover all five languages on every PR (pr-checks.yml typechecks, lints, tests, and builds TS, Python, Rust, Go, and .NET), and release.yml publishes all five: npm → PyPI → crates.io → Go module tag → NuGet.


🔎 Looking for the desktop Log Viewer?

It moved. The Rust/egui log viewer that used to live here has been rebuilt as SmooAI Observability Studio — a Dioxus native desktop client for SmooAI logs, errors, and metrics — and lives in the SmooAI/observability repo (desktop/), with builds on its releases page. The crate has been deleted from this repo; see log-viewer/DEPRECATED.md for the migration story.

🧩 Part of Smoo AI

@smooai/logger is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

Use them in your stack, or take them as a reference for how we build.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases — add one with pnpm changeset, then open a pull request referencing any related issues.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Multi-language structured logging (TypeScript, Python, Rust, Go) for AWS Lambda and browser — correlation tracking, automatic context gathering, and a Rust/egui desktop log viewer.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@smooai/logger — Contextual logging for AWS and the browser

npmPyPIcrates.ioNuGet

Smoo AIlicense

PR Checks (all 5 languages)Release

TypeScriptPythonRustGo.NET

What it is · Feature tour · Install · Quickstart · Language matrix · Platform


A log line that only carries the message is a clue. One that carries the whole story is an answer.@smooai/logger stamps every entry with the request journey (correlation IDs across services), the AWS runtime around it (Lambda, SQS, API Gateway context), and — where an OpenTelemetry span is active — the real W3C trace and span IDs, so logs join your traces instead of floating beside them. Native ports in five languages — TypeScript, Python, Rust, Go, and .NET — emit the same JSON shape, so a request crossing language boundaries still reads as one story.

Traditional loggers give you the message, but not the story. @smooai/logger records where the log came from, the request journey that led there, and the runtime around it — so a production failure reads like a trace, not a guess.


What is this?

One structured-logging schema, implemented natively five times. Each port is idiomatic in its own language, but the wire shape — level names, correlationId, http.request/response, user, telemetry, error serialization — is shared, so logs from a TypeScript Lambda, a Go worker, a Python API, a Rust service, and a .NET job land in the same queries.

  • TypeScript (src/) — the original. AWS server logging plus the only browser logger (device/browser detection, fetch correlation).
  • Python (python/) — full port, plus Socket.IO and Uvicorn logging adapters.
  • Rust (rust/logger/) — serde-based port; Lambda context helpers behind an aws-lambda feature flag.
  • Go (go/) — full port on log/slog, including Lambda/SQS helpers and OTel span correlation.
  • .NET (dotnet/) — full port; integrates with Microsoft.Extensions.Logging, trace correlation via System.Diagnostics.Activity.

The ports are not all identical — the honest capability matrix is below.


Feature tour

CapabilityWhere
🔗Correlation across servicesAll 5 languages
AWS context, captured automaticallyAll 5 languages
🔭Logs that join your tracesTS · Python · Rust · Go (+ .NET via Activity)
📍Exact caller locationAll 5 languages
🎨Pretty local output + rotating file logsAll 5 languages
🕶️Sensitive-key redactionAll 5 languages
🖥️Browser loggingTypeScript only

🔗 Correlation across services

A correlation ID set (or extracted from an incoming header, Lambda event, or SQS record) follows the request everywhere, in every language:

// Service A: API Gateway handler (TypeScript)logger.addLambdaContext(event,context);logger.info("Request received");// correlationId: abc-123// Service B: SQS processor (extracts the ID from the record)logger.addSQSRecordContext(record);logger.info("Processing message");// same correlationId: abc-123
// Service C: a Go worker — same schema, same IDl.AddHTTPRequest(logger.HTTPRequest{
Headers: map[string]string{"X-Correlation-Id": "abc-123"},
})
l.Info("Completing workflow", logger.Map{"orderId": "ord_1"}) // still abc-123
%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
B["Browser<br/>BrowserLogger (TS)"] -->|"X-Correlation-Id: abc-123"| GW["API Gateway → Lambda<br/>AwsServerLogger (TS)"]
GW -->|"SQS message attributes"| Q["SQS worker<br/>(Go port)"]
Q -->|"HTTP header"| API["Internal API<br/>(Python / .NET / Rust port)"]
GW -.-> LOGS[("One query:<br/>correlationId = abc-123")]
Q -.-> LOGS
API -.-> LOGS
B -.-> LOGS
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class LOGS warm
class B teal
Loading

⚡ AWS context, captured automatically

Hand the logger the Lambda event/context (or SQS record, or HTTP request) once; every subsequent line carries the invocation metadata — function, region, request IDs, HTTP method/path/headers, SQS attributes.

import{AwsServerLogger}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "UserAPI"});exportconsthandler=async(event,context)=>{logger.addLambdaContext(event,context);try{constuser=awaitcreateUser(event.body);logger.info("User created successfully",{userId: user.id});return{statusCode: 201,body: JSON.stringify(user)};}catch(error){logger.error("Failed to create user",error,{body: event.body});throwerror;}};

The same helpers exist in each port: add_lambda_context (Python), NewLambdaLogger / AddSQSRecordContext (Go), AddLambdaContext (.NET), and Lambda environment/event helpers behind the aws-lambda feature (Rust).

🔭 Logs that join your traces

Historically every line's traceId was a fabricated UUID — useless for joining logs to traces. Now, when an OpenTelemetry span is active, TypeScript, Python, Rust, and Go stamp the span's real W3C trace_id and span_id onto the line, matching what your tracing backend recorded. No active span → the UUID fallback, unchanged.

// Go: thread the context and the active span's IDs land on the linel.InfoContext(ctx, "Order shipped", logger.Map{"orderId": "ord_1"})
// → { "traceId": "4bf92f35…", "spanId": "00f067aa…", … }

Each port depends only on the OTel API (no SDK, no exporter). .NET is the one exception: it takes no OpenTelemetry dependency at all and reads the same real W3C IDs from System.Diagnostics.Activity.Current — the API OTel .NET itself builds on — so the output is equivalent.

📍 Exact caller location

Every entry includes where in the code it was emitted, in all five languages:

{
"callerContext": {
"stack": [
"at UserService.createUser (/src/services/UserService.ts:42:16)",
"at processRequest (/src/handlers/userHandler.ts:15:23)",
],
},
}

Two shapes are in play, and the difference is deliberate:

shapeportshow
callerContext.stack — multiple framesTypeScript, Pythonwalks the runtime stack
caller: { file, line, function } — one frameGo, Rust, .NETzero-cost compile-time / runtime.Caller
{ "caller": { "file": "UserService.cs", "line": 42, "function": "CreateUser" } }

Rust omits function: #[track_caller] gives file and line for free, but std::panic::Location carries no symbol name and resolving one would mean capturing a backtrace on every line. .NET uses [CallerFilePath]/[CallerLineNumber]/[CallerMemberName], which the compiler fills in at each call site — no StackTrace walk. Both emit the file basename only; the full path is build-machine noise.

🎨 Pretty local output + rotating file logs

All five ports detect local development and switch from strict JSON lines to ANSI pretty-printing — and write logs to disk under .smooai-logs/ with size/interval-based rotation:

constlogger=newAwsServerLogger({prettyPrint: true,// auto-enabled locallyrotation: {size: "10M",interval: "1d",compress: true},});

🕶️ Sensitive-key redaction

Every port scrubs values whose keys match a redaction list (case-insensitive, recursive) before a line is emitted — password, token, authorization, and friends by default, extensible per logger (addRedactKeys / add_redact_keys / DefaultRedactKeys…).

🖥️ Browser logging

TypeScript only.BrowserLogger captures device type, browser name/version, platform, and user agent, and correlates fetches to your backend logs:

import{BrowserLogger}from"@smooai/logger/browser/BrowserLogger";constlogger=newBrowserLogger({name: "CheckoutFlow"});constresponse=awaitfetch("/api/checkout",{method: "POST",headers: {"X-Correlation-Id": logger.correlationId()},});logger.addResponseContext(response);logger.info("Checkout completed",{orderId: data.id});

📦 Install

LanguagePackageInstall
TypeScript@smooai/loggerpnpm add @smooai/logger
Pythonsmooai-loggerpip install smooai-logger (or uv add smooai-logger)
Rustsmooai-loggercargo add smooai-logger
Gogithub.com/SmooAI/logger/go/v4go get github.com/SmooAI/logger/go/v4
.NETSmooAI.Loggerdotnet add package SmooAI.Logger

🚀 Quickstart

TypeScript (the original port — see AWS context and browser above for fuller examples):

// AWS environments (Lambda, ECS, EC2, …)import{AwsServerLogger,Level}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "OrderService",level: Level.Info});logger.addUserContext({id: "user-123",role: "admin"});// persists across logslogger.addTelemetryFields({duration: 150,operation: "db-query"});logger.info("Payment processed",{amount: 99.99,currency: "USD"});try{awaitriskyOperation();}catch(error){logger.error("Operation failed",error,{context: "additional-info"});// → error message, stack trace, error type, and your context}

Six levels in every port — TRACE · DEBUG · INFO · WARN · ERROR · FATAL — plus context presets (MINIMAL / FULL).

Per-language quickstarts, with full API docs:


One schema, five ports

The wire schema is shared; port depth is not identical. Here's the honest status of each surface:

CapabilityTypeScriptPythonRustGo.NET
Structured JSON, 6 levels
Correlation / request / trace IDs
HTTP request/response context
User context + telemetry fields
Lambda / SQS / API Gateway helpers✅ ¹
Pretty local output
Rotating file logs (.smooai-logs/)
Sensitive-key redaction
OTel span → traceId/spanId stamping➖ ²
Per-line caller location ⁴
Browser logger
Parity corpus enforced in tests ³

¹ Behind the aws-lambda cargo feature; Lambda environment context needs no feature. ² No OTel dependency — equivalent real W3C trace/span IDs read from System.Diagnostics.Activity.Current, which is the API OpenTelemetry .NET itself builds on. Log lines also tee upstream via SmooLoggerOptions.ForwardTo (an ILogger), the hook OTel's .NET log appender attaches to. ³ parity-corpus.json is the cross-language output contract, and all five ports now replay it from that one committed file — TypeScript (src/parity-corpus.spec.ts), Python (python/tests/test_parity_corpus.py), Rust (rust/logger/tests/parity_corpus.rs), Go (go/parity_corpus_test.go), and .NET (dotnet/tests/SmooAI.Logger.Tests/ParityCorpusTests.cs). It covers level mapping, required field names, message shape, correlation-id propagation, and the default redaction key list. Editing a corpus value turns all five suites red. ⁴ Two shapes: TypeScript and Python emit a multi-frame callerContext.stack; Go, Rust and .NET emit a single-frame caller object. Rust's omits function — see Exact caller location.

CI does cover all five languages on every PR (pr-checks.yml typechecks, lints, tests, and builds TS, Python, Rust, Go, and .NET), and release.yml publishes all five: npm → PyPI → crates.io → Go module tag → NuGet.


🔎 Looking for the desktop Log Viewer?

It moved. The Rust/egui log viewer that used to live here has been rebuilt as SmooAI Observability Studio — a Dioxus native desktop client for SmooAI logs, errors, and metrics — and lives in the SmooAI/observability repo (desktop/), with builds on its releases page. The crate has been deleted from this repo; see log-viewer/DEPRECATED.md for the migration story.

🧩 Part of Smoo AI

@smooai/logger is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

Use them in your stack, or take them as a reference for how we build.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases — add one with pnpm changeset, then open a pull request referencing any related issues.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Multi-language structured logging (TypeScript, Python, Rust, Go) for AWS Lambda and browser — correlation tracking, automatic context gathering, and a Rust/egui desktop log viewer.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

@smooai/logger — Contextual logging for AWS and the browser

npmPyPIcrates.ioNuGet

Smoo AIlicense

PR Checks (all 5 languages)Release

TypeScriptPythonRustGo.NET

What it is · Feature tour · Install · Quickstart · Language matrix · Platform


A log line that only carries the message is a clue. One that carries the whole story is an answer.@smooai/logger stamps every entry with the request journey (correlation IDs across services), the AWS runtime around it (Lambda, SQS, API Gateway context), and — where an OpenTelemetry span is active — the real W3C trace and span IDs, so logs join your traces instead of floating beside them. Native ports in five languages — TypeScript, Python, Rust, Go, and .NET — emit the same JSON shape, so a request crossing language boundaries still reads as one story.

Traditional loggers give you the message, but not the story. @smooai/logger records where the log came from, the request journey that led there, and the runtime around it — so a production failure reads like a trace, not a guess.


What is this?

One structured-logging schema, implemented natively five times. Each port is idiomatic in its own language, but the wire shape — level names, correlationId, http.request/response, user, telemetry, error serialization — is shared, so logs from a TypeScript Lambda, a Go worker, a Python API, a Rust service, and a .NET job land in the same queries.

  • TypeScript (src/) — the original. AWS server logging plus the only browser logger (device/browser detection, fetch correlation).
  • Python (python/) — full port, plus Socket.IO and Uvicorn logging adapters.
  • Rust (rust/logger/) — serde-based port; Lambda context helpers behind an aws-lambda feature flag.
  • Go (go/) — full port on log/slog, including Lambda/SQS helpers and OTel span correlation.
  • .NET (dotnet/) — full port; integrates with Microsoft.Extensions.Logging, trace correlation via System.Diagnostics.Activity.

The ports are not all identical — the honest capability matrix is below.


Feature tour

CapabilityWhere
🔗Correlation across servicesAll 5 languages
AWS context, captured automaticallyAll 5 languages
🔭Logs that join your tracesTS · Python · Rust · Go (+ .NET via Activity)
📍Exact caller locationAll 5 languages
🎨Pretty local output + rotating file logsAll 5 languages
🕶️Sensitive-key redactionAll 5 languages
🖥️Browser loggingTypeScript only

🔗 Correlation across services

A correlation ID set (or extracted from an incoming header, Lambda event, or SQS record) follows the request everywhere, in every language:

// Service A: API Gateway handler (TypeScript)logger.addLambdaContext(event,context);logger.info("Request received");// correlationId: abc-123// Service B: SQS processor (extracts the ID from the record)logger.addSQSRecordContext(record);logger.info("Processing message");// same correlationId: abc-123
// Service C: a Go worker — same schema, same IDl.AddHTTPRequest(logger.HTTPRequest{
Headers: map[string]string{"X-Correlation-Id": "abc-123"},
})
l.Info("Completing workflow", logger.Map{"orderId": "ord_1"}) // still abc-123
%%{init: {'theme':'base','themeVariables':{
'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52',
'lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif',
'clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
B["Browser<br/>BrowserLogger (TS)"] -->|"X-Correlation-Id: abc-123"| GW["API Gateway → Lambda<br/>AwsServerLogger (TS)"]
GW -->|"SQS message attributes"| Q["SQS worker<br/>(Go port)"]
Q -->|"HTTP header"| API["Internal API<br/>(Python / .NET / Rust port)"]
GW -.-> LOGS[("One query:<br/>correlationId = abc-123")]
Q -.-> LOGS
API -.-> LOGS
B -.-> LOGS
classDef warm fill:#f49f0a,stroke:#ff6b6c,color:#1a0f00;
classDef teal fill:#00a6a6,stroke:#00c2c2,color:#011;
class LOGS warm
class B teal
Loading

⚡ AWS context, captured automatically

Hand the logger the Lambda event/context (or SQS record, or HTTP request) once; every subsequent line carries the invocation metadata — function, region, request IDs, HTTP method/path/headers, SQS attributes.

import{AwsServerLogger}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "UserAPI"});exportconsthandler=async(event,context)=>{logger.addLambdaContext(event,context);try{constuser=awaitcreateUser(event.body);logger.info("User created successfully",{userId: user.id});return{statusCode: 201,body: JSON.stringify(user)};}catch(error){logger.error("Failed to create user",error,{body: event.body});throwerror;}};

The same helpers exist in each port: add_lambda_context (Python), NewLambdaLogger / AddSQSRecordContext (Go), AddLambdaContext (.NET), and Lambda environment/event helpers behind the aws-lambda feature (Rust).

🔭 Logs that join your traces

Historically every line's traceId was a fabricated UUID — useless for joining logs to traces. Now, when an OpenTelemetry span is active, TypeScript, Python, Rust, and Go stamp the span's real W3C trace_id and span_id onto the line, matching what your tracing backend recorded. No active span → the UUID fallback, unchanged.

// Go: thread the context and the active span's IDs land on the linel.InfoContext(ctx, "Order shipped", logger.Map{"orderId": "ord_1"})
// → { "traceId": "4bf92f35…", "spanId": "00f067aa…", … }

Each port depends only on the OTel API (no SDK, no exporter). .NET is the one exception: it takes no OpenTelemetry dependency at all and reads the same real W3C IDs from System.Diagnostics.Activity.Current — the API OTel .NET itself builds on — so the output is equivalent.

📍 Exact caller location

Every entry includes where in the code it was emitted, in all five languages:

{
"callerContext": {
"stack": [
"at UserService.createUser (/src/services/UserService.ts:42:16)",
"at processRequest (/src/handlers/userHandler.ts:15:23)",
],
},
}

Two shapes are in play, and the difference is deliberate:

shapeportshow
callerContext.stack — multiple framesTypeScript, Pythonwalks the runtime stack
caller: { file, line, function } — one frameGo, Rust, .NETzero-cost compile-time / runtime.Caller
{ "caller": { "file": "UserService.cs", "line": 42, "function": "CreateUser" } }

Rust omits function: #[track_caller] gives file and line for free, but std::panic::Location carries no symbol name and resolving one would mean capturing a backtrace on every line. .NET uses [CallerFilePath]/[CallerLineNumber]/[CallerMemberName], which the compiler fills in at each call site — no StackTrace walk. Both emit the file basename only; the full path is build-machine noise.

🎨 Pretty local output + rotating file logs

All five ports detect local development and switch from strict JSON lines to ANSI pretty-printing — and write logs to disk under .smooai-logs/ with size/interval-based rotation:

constlogger=newAwsServerLogger({prettyPrint: true,// auto-enabled locallyrotation: {size: "10M",interval: "1d",compress: true},});

🕶️ Sensitive-key redaction

Every port scrubs values whose keys match a redaction list (case-insensitive, recursive) before a line is emitted — password, token, authorization, and friends by default, extensible per logger (addRedactKeys / add_redact_keys / DefaultRedactKeys…).

🖥️ Browser logging

TypeScript only.BrowserLogger captures device type, browser name/version, platform, and user agent, and correlates fetches to your backend logs:

import{BrowserLogger}from"@smooai/logger/browser/BrowserLogger";constlogger=newBrowserLogger({name: "CheckoutFlow"});constresponse=awaitfetch("/api/checkout",{method: "POST",headers: {"X-Correlation-Id": logger.correlationId()},});logger.addResponseContext(response);logger.info("Checkout completed",{orderId: data.id});

📦 Install

LanguagePackageInstall
TypeScript@smooai/loggerpnpm add @smooai/logger
Pythonsmooai-loggerpip install smooai-logger (or uv add smooai-logger)
Rustsmooai-loggercargo add smooai-logger
Gogithub.com/SmooAI/logger/go/v4go get github.com/SmooAI/logger/go/v4
.NETSmooAI.Loggerdotnet add package SmooAI.Logger

🚀 Quickstart

TypeScript (the original port — see AWS context and browser above for fuller examples):

// AWS environments (Lambda, ECS, EC2, …)import{AwsServerLogger,Level}from"@smooai/logger/AwsServerLogger";constlogger=newAwsServerLogger({name: "OrderService",level: Level.Info});logger.addUserContext({id: "user-123",role: "admin"});// persists across logslogger.addTelemetryFields({duration: 150,operation: "db-query"});logger.info("Payment processed",{amount: 99.99,currency: "USD"});try{awaitriskyOperation();}catch(error){logger.error("Operation failed",error,{context: "additional-info"});// → error message, stack trace, error type, and your context}

Six levels in every port — TRACE · DEBUG · INFO · WARN · ERROR · FATAL — plus context presets (MINIMAL / FULL).

Per-language quickstarts, with full API docs:


One schema, five ports

The wire schema is shared; port depth is not identical. Here's the honest status of each surface:

CapabilityTypeScriptPythonRustGo.NET
Structured JSON, 6 levels
Correlation / request / trace IDs
HTTP request/response context
User context + telemetry fields
Lambda / SQS / API Gateway helpers✅ ¹
Pretty local output
Rotating file logs (.smooai-logs/)
Sensitive-key redaction
OTel span → traceId/spanId stamping➖ ²
Per-line caller location ⁴
Browser logger
Parity corpus enforced in tests ³

¹ Behind the aws-lambda cargo feature; Lambda environment context needs no feature. ² No OTel dependency — equivalent real W3C trace/span IDs read from System.Diagnostics.Activity.Current, which is the API OpenTelemetry .NET itself builds on. Log lines also tee upstream via SmooLoggerOptions.ForwardTo (an ILogger), the hook OTel's .NET log appender attaches to. ³ parity-corpus.json is the cross-language output contract, and all five ports now replay it from that one committed file — TypeScript (src/parity-corpus.spec.ts), Python (python/tests/test_parity_corpus.py), Rust (rust/logger/tests/parity_corpus.rs), Go (go/parity_corpus_test.go), and .NET (dotnet/tests/SmooAI.Logger.Tests/ParityCorpusTests.cs). It covers level mapping, required field names, message shape, correlation-id propagation, and the default redaction key list. Editing a corpus value turns all five suites red. ⁴ Two shapes: TypeScript and Python emit a multi-frame callerContext.stack; Go, Rust and .NET emit a single-frame caller object. Rust's omits function — see Exact caller location.

CI does cover all five languages on every PR (pr-checks.yml typechecks, lints, tests, and builds TS, Python, Rust, Go, and .NET), and release.yml publishes all five: npm → PyPI → crates.io → Go module tag → NuGet.


🔎 Looking for the desktop Log Viewer?

It moved. The Rust/egui log viewer that used to live here has been rebuilt as SmooAI Observability Studio — a Dioxus native desktop client for SmooAI logs, errors, and metrics — and lives in the SmooAI/observability repo (desktop/), with builds on its releases page. The crate has been deleted from this repo; see log-viewer/DEPRECATED.md for the migration story.

🧩 Part of Smoo AI

@smooai/logger is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

Use them in your stack, or take them as a reference for how we build.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases — add one with pnpm changeset, then open a pull request referencing any related issues.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Multi-language structured logging (TypeScript, Python, Rust, Go) for AWS Lambda and browser — correlation tracking, automatic context gathering, and a Rust/egui desktop log viewer.

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages