@photon-ai/otel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +267 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @photon-ai/otel
|
|
2
|
+
|
|
3
|
+
A DX-focused OpenTelemetry wrapper for **Bun** and **Node.js**.
|
|
4
|
+
|
|
5
|
+
Vanilla OTel works, but the setup is verbose, the logger plumbing is awkward, and PII scrubbing is on you. `@photon-ai/otel` wraps the OTLP/HTTP stack into a few well-named functions:
|
|
6
|
+
|
|
7
|
+
- **`setupOtel()`** — idempotent one-call bootstrap for traces + logs. Honors all standard `OTEL_EXPORTER_OTLP_*` env vars.
|
|
8
|
+
- **`createLogger(module)`** — structured logger that writes to both the OTel logger provider and `console`, with automatic trace correlation and exception capture.
|
|
9
|
+
- **`withSpan(name, attrs?, fn)`** — wrap any sync or async function in a span; errors are recorded and PII in the error message is scrubbed before being attached to span status.
|
|
10
|
+
- **`sanitizeEmail` / `sanitizePhone` / `sanitizeErrorMessage`** — PII helpers you can reuse anywhere.
|
|
11
|
+
|
|
12
|
+
OTLP/HTTP only (no gRPC, no proto). Works identically on Bun and Node ≥ 20.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add @photon-ai/otel
|
|
18
|
+
# or
|
|
19
|
+
npm install @photon-ai/otel
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { createLogger, setupOtel, withSpan } from "@photon-ai/otel";
|
|
26
|
+
|
|
27
|
+
setupOtel({
|
|
28
|
+
serviceName: "my-service",
|
|
29
|
+
serviceVersion: "1.0.0",
|
|
30
|
+
endpoint: "https://otel.example.com", // optional; env var wins
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const log = createLogger("server");
|
|
34
|
+
|
|
35
|
+
await withSpan("handle-request", { route: "/users" }, async () => {
|
|
36
|
+
log.info("processing request", { userId: 42 });
|
|
37
|
+
// ... your work
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If `OTEL_EXPORTER_OTLP_ENDPOINT` (or the `endpoint` option) is unset, `setupOtel()` still runs but exporters are no-ops — perfect for local development with zero config.
|
|
42
|
+
|
|
43
|
+
## API
|
|
44
|
+
|
|
45
|
+
| Function | Description |
|
|
46
|
+
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| `setupOtel(options): OtelHandle` | Boots OTLP/HTTP traces + logs. Idempotent. Returns `{ shutdown(): Promise<void> }`. |
|
|
48
|
+
| `isOtelActive(): boolean` | Returns `true` if `setupOtel` has already run in this process. |
|
|
49
|
+
| `createLogger(module): PhotonLogger` | Returns `{ info, warn, error, debug }`. Each call emits to OTel + `console`, correlates to active span. |
|
|
50
|
+
| `withSpan(name, fn)` | Wraps `fn` (sync or async) in a span. Records exceptions and scrubs PII in error messages. |
|
|
51
|
+
| `withSpan(name, attrs, fn)` | Same as above but attaches `attrs` to the span. |
|
|
52
|
+
| `sanitizeEmail(input)` | Masks an email: `foo.bar@example.com` → `fo***@e***.com`. |
|
|
53
|
+
| `sanitizePhone(input)` | Masks a phone: `+13315553374` → `+133xxxxx3374`. |
|
|
54
|
+
| `sanitizeErrorMessage(input)` | Masks every email and phone embedded in a free-form string. |
|
|
55
|
+
| `PHOTON_OTEL_VERSION` | Constant — current package version. |
|
|
56
|
+
|
|
57
|
+
### Logger signatures
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
log.info(message, attrs?);
|
|
61
|
+
log.warn(message, attrs?);
|
|
62
|
+
log.error(message, attrs?, error?); // only error() accepts an exception
|
|
63
|
+
log.debug(message, attrs?);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`attrs` is `Record<string, string | number | boolean | undefined>`. `undefined` values are dropped.
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
Standard OpenTelemetry env vars always take precedence over `SetupOtelOptions`:
|
|
71
|
+
|
|
72
|
+
| Variable | Effect |
|
|
73
|
+
| ----------------------------------------- | ------------------------------------------------------- |
|
|
74
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Base endpoint; `/v1/traces` and `/v1/logs` auto-appended. |
|
|
75
|
+
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Full traces URL (overrides the base for traces). |
|
|
76
|
+
| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Full logs URL (overrides the base for logs). |
|
|
77
|
+
| `OTEL_EXPORTER_OTLP_HEADERS` | `key=value,key=value` headers; merged with `options.headers` (env wins). |
|
|
78
|
+
| `DEPLOYMENT_ENV` | Attached as `deployment.environment` resource attribute. Defaults to `development`. |
|
|
79
|
+
|
|
80
|
+
## Running on Node vs Bun
|
|
81
|
+
|
|
82
|
+
The same code runs unmodified on both. Pick whichever you prefer:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
bun run src/server.ts
|
|
86
|
+
# or
|
|
87
|
+
node --experimental-strip-types src/server.ts
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The package uses `process.env` (not `Bun.env`) and `AsyncLocalStorageContextManager` (backed by `async_hooks`), both of which are supported natively by Bun and Node ≥ 20.
|
|
91
|
+
|
|
92
|
+
For Bun consumers, the `exports` map points at the TypeScript source via the `bun` condition for faster cold starts during dev. Node consumers get the pre-built ESM bundle.
|
|
93
|
+
|
|
94
|
+
## Why HTTP exporters only?
|
|
95
|
+
|
|
96
|
+
- Bun's gRPC support is incomplete in some paths — HTTP is reliable everywhere.
|
|
97
|
+
- JSON-over-HTTP is trivial to debug with `curl` and a packet sniffer.
|
|
98
|
+
- One fewer transport keeps the dependency surface small.
|
|
99
|
+
|
|
100
|
+
If you need gRPC, instantiate your own exporter and processor with `@opentelemetry/api` directly — `setupOtel()` is opinionated, not a wall.
|
|
101
|
+
|
|
102
|
+
## Framework integration
|
|
103
|
+
|
|
104
|
+
This package is framework-agnostic. For Elysia.js (Bun-native), see the [`elysiajs-otel` skill](https://github.com/anthropics/claude-code) for a recipe combining `@photon-ai/otel` with `@elysiajs/opentelemetry` auto-instrumentation. A dedicated `@photon-ai/otel-elysia` package may follow.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
type LogAttrs = Record<string, string | number | boolean | undefined>;
|
|
2
|
+
interface PhotonLogger {
|
|
3
|
+
debug(message: string, attrs?: LogAttrs): void;
|
|
4
|
+
error(message: string, attrs?: LogAttrs, error?: unknown): void;
|
|
5
|
+
info(message: string, attrs?: LogAttrs): void;
|
|
6
|
+
warn(message: string, attrs?: LogAttrs): void;
|
|
7
|
+
}
|
|
8
|
+
declare function createLogger(module: string): PhotonLogger;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mask a phone number, keeping the leading `+` (if any) plus the first 3 digits
|
|
12
|
+
* and the last 4 digits visible. Example: `+13315553374` -> `+133xxxxx3374`.
|
|
13
|
+
*
|
|
14
|
+
* Inputs that don't have enough digits to safely mask are returned as
|
|
15
|
+
* `xxxx` to avoid leaking the entire short value.
|
|
16
|
+
*/
|
|
17
|
+
declare function sanitizePhone(input: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Mask an email address, keeping the first 2 chars of the local part, the
|
|
20
|
+
* first char of the domain, and the TLD. Example:
|
|
21
|
+
* `foo.bar@example.com` -> `fo***@e***.com`.
|
|
22
|
+
*/
|
|
23
|
+
declare function sanitizeEmail(input: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Replace every phone number and email address inside a free-form string with
|
|
26
|
+
* its sanitized form. Used to scrub `Error.message` values before attaching
|
|
27
|
+
* them to span status.
|
|
28
|
+
*/
|
|
29
|
+
declare function sanitizeErrorMessage(input: string): string;
|
|
30
|
+
|
|
31
|
+
interface SetupOtelOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Default OTLP/HTTP base endpoint (e.g. `https://otel.example.com`). The
|
|
34
|
+
* `/v1/traces` and `/v1/logs` paths are appended automatically. Standard
|
|
35
|
+
* `OTEL_EXPORTER_OTLP_*` env vars always take precedence.
|
|
36
|
+
*/
|
|
37
|
+
endpoint?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Default OTLP headers (e.g. `{ Authorization: "Basic ..." }`). Merged with
|
|
40
|
+
* any headers parsed from `OTEL_EXPORTER_OTLP_HEADERS`; env values win on
|
|
41
|
+
* conflicts.
|
|
42
|
+
*/
|
|
43
|
+
headers?: Record<string, string>;
|
|
44
|
+
/**
|
|
45
|
+
* Extra resource attributes attached to every span/log alongside
|
|
46
|
+
* `service.name` / `service.version`.
|
|
47
|
+
*/
|
|
48
|
+
resourceAttributes?: Record<string, string | number | boolean>;
|
|
49
|
+
serviceName: string;
|
|
50
|
+
serviceVersion?: string;
|
|
51
|
+
}
|
|
52
|
+
interface OtelHandle {
|
|
53
|
+
shutdown(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Boot an OTLP/HTTP-based OpenTelemetry pipeline (traces + logs).
|
|
57
|
+
*
|
|
58
|
+
* Idempotent: calling twice in the same process is a no-op on the second
|
|
59
|
+
* call, so libraries can safely invoke this without clobbering an app-level
|
|
60
|
+
* OTel setup that ran earlier.
|
|
61
|
+
*
|
|
62
|
+
* Standard `OTEL_EXPORTER_OTLP_*` env vars override the `endpoint` and
|
|
63
|
+
* `headers` arguments — this matches the OpenTelemetry SDK config spec.
|
|
64
|
+
*/
|
|
65
|
+
declare function setupOtel(options: SetupOtelOptions): OtelHandle;
|
|
66
|
+
/**
|
|
67
|
+
* Read-only accessor for tests / debug paths that need to know whether
|
|
68
|
+
* `setupOtel` has already run in this process.
|
|
69
|
+
*/
|
|
70
|
+
declare function isOtelActive(): boolean;
|
|
71
|
+
|
|
72
|
+
declare const PHOTON_OTEL_VERSION = "0.1.0";
|
|
73
|
+
|
|
74
|
+
declare function withSpan<T>(name: string, fn: () => Promise<T> | T): Promise<T>;
|
|
75
|
+
declare function withSpan<T>(name: string, attrs: LogAttrs, fn: () => Promise<T> | T): Promise<T>;
|
|
76
|
+
|
|
77
|
+
export { type LogAttrs, type OtelHandle, PHOTON_OTEL_VERSION, type PhotonLogger, type SetupOtelOptions, createLogger, isOtelActive, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, setupOtel, withSpan };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
import { context as otelContext } from "@opentelemetry/api";
|
|
3
|
+
import { logs, SeverityNumber } from "@opentelemetry/api-logs";
|
|
4
|
+
|
|
5
|
+
// src/version.ts
|
|
6
|
+
var PHOTON_OTEL_VERSION = "0.1.0";
|
|
7
|
+
|
|
8
|
+
// src/logger.ts
|
|
9
|
+
var scopedLogger;
|
|
10
|
+
function getLogger() {
|
|
11
|
+
if (!scopedLogger) {
|
|
12
|
+
scopedLogger = logs.getLogger("@photon-ai/otel", PHOTON_OTEL_VERSION);
|
|
13
|
+
}
|
|
14
|
+
return scopedLogger;
|
|
15
|
+
}
|
|
16
|
+
function filterUndefined(attrs) {
|
|
17
|
+
if (!attrs) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
22
|
+
if (v !== void 0) {
|
|
23
|
+
out[k] = v;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function emit(severityNumber, severityText, module, message, attrs, error) {
|
|
29
|
+
const attributes = {
|
|
30
|
+
"log.module": module,
|
|
31
|
+
...filterUndefined(attrs)
|
|
32
|
+
};
|
|
33
|
+
if (error instanceof Error) {
|
|
34
|
+
attributes["exception.type"] = error.name;
|
|
35
|
+
attributes["exception.message"] = error.message;
|
|
36
|
+
if (error.stack) {
|
|
37
|
+
attributes["exception.stacktrace"] = error.stack;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
getLogger().emit({
|
|
41
|
+
severityNumber,
|
|
42
|
+
severityText,
|
|
43
|
+
body: message,
|
|
44
|
+
attributes,
|
|
45
|
+
context: otelContext.active()
|
|
46
|
+
});
|
|
47
|
+
const prefix = `[${module}]`;
|
|
48
|
+
if (severityNumber >= SeverityNumber.ERROR) {
|
|
49
|
+
console.error(prefix, message, ...error ? [error] : []);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(prefix, message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function createLogger(module) {
|
|
55
|
+
return {
|
|
56
|
+
info: (message, attrs) => emit(SeverityNumber.INFO, "INFO", module, message, attrs),
|
|
57
|
+
warn: (message, attrs) => emit(SeverityNumber.WARN, "WARN", module, message, attrs),
|
|
58
|
+
error: (message, attrs, error) => emit(SeverityNumber.ERROR, "ERROR", module, message, attrs, error),
|
|
59
|
+
debug: (message, attrs) => emit(SeverityNumber.DEBUG, "DEBUG", module, message, attrs)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/sanitize.ts
|
|
64
|
+
var PHONE_PATTERN = /\+?\d[\d\s()\-.]{6,18}\d/g;
|
|
65
|
+
var EMAIL_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
66
|
+
function sanitizePhone(input) {
|
|
67
|
+
const hasPlus = input.startsWith("+");
|
|
68
|
+
const digits = input.replace(/\D/g, "");
|
|
69
|
+
if (digits.length < 8) {
|
|
70
|
+
return hasPlus ? "+xxxx" : "xxxx";
|
|
71
|
+
}
|
|
72
|
+
const head = digits.slice(0, 3);
|
|
73
|
+
const tail = digits.slice(-4);
|
|
74
|
+
const middleLength = digits.length - head.length - tail.length;
|
|
75
|
+
return `${hasPlus ? "+" : ""}${head}${"x".repeat(middleLength)}${tail}`;
|
|
76
|
+
}
|
|
77
|
+
function sanitizeEmail(input) {
|
|
78
|
+
const atIndex = input.lastIndexOf("@");
|
|
79
|
+
if (atIndex < 1) {
|
|
80
|
+
return "***";
|
|
81
|
+
}
|
|
82
|
+
const local = input.slice(0, atIndex);
|
|
83
|
+
const domain = input.slice(atIndex + 1);
|
|
84
|
+
const dotIndex = domain.lastIndexOf(".");
|
|
85
|
+
if (dotIndex < 1) {
|
|
86
|
+
return "***";
|
|
87
|
+
}
|
|
88
|
+
const localHead = local.slice(0, 2);
|
|
89
|
+
const domainHead = domain.slice(0, 1);
|
|
90
|
+
const tld = domain.slice(dotIndex);
|
|
91
|
+
return `${localHead}***@${domainHead}***${tld}`;
|
|
92
|
+
}
|
|
93
|
+
function sanitizeErrorMessage(input) {
|
|
94
|
+
return input.replace(EMAIL_PATTERN, (match) => sanitizeEmail(match)).replace(PHONE_PATTERN, (match) => sanitizePhone(match));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/setup.ts
|
|
98
|
+
import { context, trace } from "@opentelemetry/api";
|
|
99
|
+
import { logs as logs2 } from "@opentelemetry/api-logs";
|
|
100
|
+
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
|
|
101
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
102
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
103
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
104
|
+
import {
|
|
105
|
+
BatchLogRecordProcessor,
|
|
106
|
+
LoggerProvider
|
|
107
|
+
} from "@opentelemetry/sdk-logs";
|
|
108
|
+
import {
|
|
109
|
+
BasicTracerProvider,
|
|
110
|
+
BatchSpanProcessor
|
|
111
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
112
|
+
var activeHandle;
|
|
113
|
+
var TRAILING_SLASH = /\/$/;
|
|
114
|
+
function parseEnvHeaders(raw) {
|
|
115
|
+
if (!raw) {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
const out = {};
|
|
119
|
+
for (const pair of raw.split(",")) {
|
|
120
|
+
const eq = pair.indexOf("=");
|
|
121
|
+
if (eq <= 0) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const key = pair.slice(0, eq).trim();
|
|
125
|
+
const value = pair.slice(eq + 1).trim();
|
|
126
|
+
if (key) {
|
|
127
|
+
out[key] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function resolveTracesEndpoint(base) {
|
|
133
|
+
const traces = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
|
|
134
|
+
if (traces) {
|
|
135
|
+
return traces;
|
|
136
|
+
}
|
|
137
|
+
const generic = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? base;
|
|
138
|
+
return generic ? `${generic.replace(TRAILING_SLASH, "")}/v1/traces` : void 0;
|
|
139
|
+
}
|
|
140
|
+
function resolveLogsEndpoint(base) {
|
|
141
|
+
const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
|
|
142
|
+
if (logsEndpoint) {
|
|
143
|
+
return logsEndpoint;
|
|
144
|
+
}
|
|
145
|
+
const generic = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? base;
|
|
146
|
+
return generic ? `${generic.replace(TRAILING_SLASH, "")}/v1/logs` : void 0;
|
|
147
|
+
}
|
|
148
|
+
function setupOtel(options) {
|
|
149
|
+
if (activeHandle) {
|
|
150
|
+
return activeHandle;
|
|
151
|
+
}
|
|
152
|
+
const tracesEndpoint = resolveTracesEndpoint(options.endpoint);
|
|
153
|
+
const logsEndpoint = resolveLogsEndpoint(options.endpoint);
|
|
154
|
+
const mergedHeaders = {
|
|
155
|
+
...options.headers,
|
|
156
|
+
...parseEnvHeaders(process.env.OTEL_EXPORTER_OTLP_HEADERS)
|
|
157
|
+
};
|
|
158
|
+
const hasHeaders = Object.keys(mergedHeaders).length > 0;
|
|
159
|
+
const resource = resourceFromAttributes({
|
|
160
|
+
"service.name": options.serviceName,
|
|
161
|
+
...options.serviceVersion ? { "service.version": options.serviceVersion } : {},
|
|
162
|
+
"deployment.environment": process.env.DEPLOYMENT_ENV ?? "development",
|
|
163
|
+
...options.resourceAttributes
|
|
164
|
+
});
|
|
165
|
+
context.setGlobalContextManager(new AsyncLocalStorageContextManager());
|
|
166
|
+
const traceProcessors = tracesEndpoint ? [
|
|
167
|
+
new BatchSpanProcessor(
|
|
168
|
+
new OTLPTraceExporter({
|
|
169
|
+
url: tracesEndpoint,
|
|
170
|
+
headers: hasHeaders ? mergedHeaders : void 0
|
|
171
|
+
})
|
|
172
|
+
)
|
|
173
|
+
] : [];
|
|
174
|
+
const tracerProvider = new BasicTracerProvider({
|
|
175
|
+
resource,
|
|
176
|
+
spanProcessors: traceProcessors
|
|
177
|
+
});
|
|
178
|
+
trace.setGlobalTracerProvider(tracerProvider);
|
|
179
|
+
const logProcessors = logsEndpoint ? [
|
|
180
|
+
new BatchLogRecordProcessor(
|
|
181
|
+
new OTLPLogExporter({
|
|
182
|
+
url: logsEndpoint,
|
|
183
|
+
headers: hasHeaders ? mergedHeaders : void 0
|
|
184
|
+
})
|
|
185
|
+
)
|
|
186
|
+
] : [];
|
|
187
|
+
const loggerProvider = new LoggerProvider({
|
|
188
|
+
resource,
|
|
189
|
+
processors: logProcessors
|
|
190
|
+
});
|
|
191
|
+
logs2.setGlobalLoggerProvider(loggerProvider);
|
|
192
|
+
const handle = {
|
|
193
|
+
async shutdown() {
|
|
194
|
+
await Promise.allSettled([
|
|
195
|
+
tracerProvider.shutdown(),
|
|
196
|
+
loggerProvider.shutdown()
|
|
197
|
+
]);
|
|
198
|
+
activeHandle = void 0;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
activeHandle = handle;
|
|
202
|
+
return handle;
|
|
203
|
+
}
|
|
204
|
+
function isOtelActive() {
|
|
205
|
+
return activeHandle !== void 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/with-span.ts
|
|
209
|
+
import {
|
|
210
|
+
SpanStatusCode,
|
|
211
|
+
trace as trace2
|
|
212
|
+
} from "@opentelemetry/api";
|
|
213
|
+
var scopedTracer;
|
|
214
|
+
function getTracer() {
|
|
215
|
+
if (!scopedTracer) {
|
|
216
|
+
scopedTracer = trace2.getTracer("@photon-ai/otel", PHOTON_OTEL_VERSION);
|
|
217
|
+
}
|
|
218
|
+
return scopedTracer;
|
|
219
|
+
}
|
|
220
|
+
function toAttributes(attrs) {
|
|
221
|
+
const out = {};
|
|
222
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
223
|
+
if (v !== void 0) {
|
|
224
|
+
out[k] = v;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
function withSpan(name, attrsOrFn, maybeFn) {
|
|
230
|
+
const fn = typeof attrsOrFn === "function" ? attrsOrFn : maybeFn;
|
|
231
|
+
if (!fn) {
|
|
232
|
+
throw new Error("withSpan: function argument is required");
|
|
233
|
+
}
|
|
234
|
+
const attrs = typeof attrsOrFn === "function" ? void 0 : attrsOrFn;
|
|
235
|
+
return getTracer().startActiveSpan(name, async (span) => {
|
|
236
|
+
if (attrs) {
|
|
237
|
+
span.setAttributes(toAttributes(attrs));
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const result = await fn();
|
|
241
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
242
|
+
return result;
|
|
243
|
+
} catch (err) {
|
|
244
|
+
span.recordException(err);
|
|
245
|
+
const errorObj = err instanceof Error ? err : void 0;
|
|
246
|
+
span.setAttribute("error.type", errorObj?.constructor.name ?? typeof err);
|
|
247
|
+
span.setStatus({
|
|
248
|
+
code: SpanStatusCode.ERROR,
|
|
249
|
+
message: errorObj ? sanitizeErrorMessage(errorObj.message) : sanitizeErrorMessage(String(err))
|
|
250
|
+
});
|
|
251
|
+
throw err;
|
|
252
|
+
} finally {
|
|
253
|
+
span.end();
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
export {
|
|
258
|
+
PHOTON_OTEL_VERSION,
|
|
259
|
+
createLogger,
|
|
260
|
+
isOtelActive,
|
|
261
|
+
sanitizeEmail,
|
|
262
|
+
sanitizeErrorMessage,
|
|
263
|
+
sanitizePhone,
|
|
264
|
+
setupOtel,
|
|
265
|
+
withSpan
|
|
266
|
+
};
|
|
267
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts","../src/version.ts","../src/sanitize.ts","../src/setup.ts","../src/with-span.ts"],"sourcesContent":["import { context as otelContext } from \"@opentelemetry/api\";\nimport { type Logger, logs, SeverityNumber } from \"@opentelemetry/api-logs\";\nimport { PHOTON_OTEL_VERSION } from \"./version\";\n\nexport type LogAttrs = Record<string, string | number | boolean | undefined>;\n\nlet scopedLogger: Logger | undefined;\n\nfunction getLogger(): Logger {\n if (!scopedLogger) {\n scopedLogger = logs.getLogger(\"@photon-ai/otel\", PHOTON_OTEL_VERSION);\n }\n return scopedLogger;\n}\n\nfunction filterUndefined(\n attrs?: LogAttrs\n): Record<string, string | number | boolean> {\n if (!attrs) {\n return {};\n }\n const out: Record<string, string | number | boolean> = {};\n for (const [k, v] of Object.entries(attrs)) {\n if (v !== undefined) {\n out[k] = v;\n }\n }\n return out;\n}\n\nfunction emit(\n severityNumber: SeverityNumber,\n severityText: string,\n module: string,\n message: string,\n attrs?: LogAttrs,\n error?: unknown\n): void {\n const attributes: Record<string, string | number | boolean> = {\n \"log.module\": module,\n ...filterUndefined(attrs),\n };\n\n if (error instanceof Error) {\n attributes[\"exception.type\"] = error.name;\n attributes[\"exception.message\"] = error.message;\n if (error.stack) {\n attributes[\"exception.stacktrace\"] = error.stack;\n }\n }\n\n getLogger().emit({\n severityNumber,\n severityText,\n body: message,\n attributes,\n context: otelContext.active(),\n });\n\n const prefix = `[${module}]`;\n if (severityNumber >= SeverityNumber.ERROR) {\n console.error(prefix, message, ...(error ? [error] : []));\n } else {\n console.log(prefix, message);\n }\n}\n\nexport interface PhotonLogger {\n debug(message: string, attrs?: LogAttrs): void;\n error(message: string, attrs?: LogAttrs, error?: unknown): void;\n info(message: string, attrs?: LogAttrs): void;\n warn(message: string, attrs?: LogAttrs): void;\n}\n\nexport function createLogger(module: string): PhotonLogger {\n return {\n info: (message, attrs) =>\n emit(SeverityNumber.INFO, \"INFO\", module, message, attrs),\n warn: (message, attrs) =>\n emit(SeverityNumber.WARN, \"WARN\", module, message, attrs),\n error: (message, attrs, error) =>\n emit(SeverityNumber.ERROR, \"ERROR\", module, message, attrs, error),\n debug: (message, attrs) =>\n emit(SeverityNumber.DEBUG, \"DEBUG\", module, message, attrs),\n };\n}\n","export const PHOTON_OTEL_VERSION = \"0.1.0\";\n","// E.164-ish phone match: optional `+`, 7–15 digits with optional separators.\nconst PHONE_PATTERN = /\\+?\\d[\\d\\s()\\-.]{6,18}\\d/g;\nconst EMAIL_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g;\n\n/**\n * Mask a phone number, keeping the leading `+` (if any) plus the first 3 digits\n * and the last 4 digits visible. Example: `+13315553374` -> `+133xxxxx3374`.\n *\n * Inputs that don't have enough digits to safely mask are returned as\n * `xxxx` to avoid leaking the entire short value.\n */\nexport function sanitizePhone(input: string): string {\n const hasPlus = input.startsWith(\"+\");\n const digits = input.replace(/\\D/g, \"\");\n if (digits.length < 8) {\n return hasPlus ? \"+xxxx\" : \"xxxx\";\n }\n const head = digits.slice(0, 3);\n const tail = digits.slice(-4);\n const middleLength = digits.length - head.length - tail.length;\n return `${hasPlus ? \"+\" : \"\"}${head}${\"x\".repeat(middleLength)}${tail}`;\n}\n\n/**\n * Mask an email address, keeping the first 2 chars of the local part, the\n * first char of the domain, and the TLD. Example:\n * `foo.bar@example.com` -> `fo***@e***.com`.\n */\nexport function sanitizeEmail(input: string): string {\n const atIndex = input.lastIndexOf(\"@\");\n if (atIndex < 1) {\n return \"***\";\n }\n const local = input.slice(0, atIndex);\n const domain = input.slice(atIndex + 1);\n const dotIndex = domain.lastIndexOf(\".\");\n if (dotIndex < 1) {\n return \"***\";\n }\n const localHead = local.slice(0, 2);\n const domainHead = domain.slice(0, 1);\n const tld = domain.slice(dotIndex);\n return `${localHead}***@${domainHead}***${tld}`;\n}\n\n/**\n * Replace every phone number and email address inside a free-form string with\n * its sanitized form. Used to scrub `Error.message` values before attaching\n * them to span status.\n */\nexport function sanitizeErrorMessage(input: string): string {\n return input\n .replace(EMAIL_PATTERN, (match) => sanitizeEmail(match))\n .replace(PHONE_PATTERN, (match) => sanitizePhone(match));\n}\n","import { context, trace } from \"@opentelemetry/api\";\nimport { logs } from \"@opentelemetry/api-logs\";\nimport { AsyncLocalStorageContextManager } from \"@opentelemetry/context-async-hooks\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-http\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n BatchLogRecordProcessor,\n LoggerProvider,\n} from \"@opentelemetry/sdk-logs\";\nimport {\n BasicTracerProvider,\n BatchSpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\n\nexport interface SetupOtelOptions {\n /**\n * Default OTLP/HTTP base endpoint (e.g. `https://otel.example.com`). The\n * `/v1/traces` and `/v1/logs` paths are appended automatically. Standard\n * `OTEL_EXPORTER_OTLP_*` env vars always take precedence.\n */\n endpoint?: string;\n /**\n * Default OTLP headers (e.g. `{ Authorization: \"Basic ...\" }`). Merged with\n * any headers parsed from `OTEL_EXPORTER_OTLP_HEADERS`; env values win on\n * conflicts.\n */\n headers?: Record<string, string>;\n /**\n * Extra resource attributes attached to every span/log alongside\n * `service.name` / `service.version`.\n */\n resourceAttributes?: Record<string, string | number | boolean>;\n serviceName: string;\n serviceVersion?: string;\n}\n\nexport interface OtelHandle {\n shutdown(): Promise<void>;\n}\n\nlet activeHandle: OtelHandle | undefined;\n\nconst TRAILING_SLASH = /\\/$/;\n\nfunction parseEnvHeaders(raw: string | undefined): Record<string, string> {\n if (!raw) {\n return {};\n }\n const out: Record<string, string> = {};\n for (const pair of raw.split(\",\")) {\n const eq = pair.indexOf(\"=\");\n if (eq <= 0) {\n continue;\n }\n const key = pair.slice(0, eq).trim();\n const value = pair.slice(eq + 1).trim();\n if (key) {\n out[key] = value;\n }\n }\n return out;\n}\n\nfunction resolveTracesEndpoint(base: string | undefined): string | undefined {\n const traces = process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;\n if (traces) {\n return traces;\n }\n const generic = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? base;\n return generic\n ? `${generic.replace(TRAILING_SLASH, \"\")}/v1/traces`\n : undefined;\n}\n\nfunction resolveLogsEndpoint(base: string | undefined): string | undefined {\n const logsEndpoint = process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;\n if (logsEndpoint) {\n return logsEndpoint;\n }\n const generic = process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? base;\n return generic ? `${generic.replace(TRAILING_SLASH, \"\")}/v1/logs` : undefined;\n}\n\n/**\n * Boot an OTLP/HTTP-based OpenTelemetry pipeline (traces + logs).\n *\n * Idempotent: calling twice in the same process is a no-op on the second\n * call, so libraries can safely invoke this without clobbering an app-level\n * OTel setup that ran earlier.\n *\n * Standard `OTEL_EXPORTER_OTLP_*` env vars override the `endpoint` and\n * `headers` arguments — this matches the OpenTelemetry SDK config spec.\n */\nexport function setupOtel(options: SetupOtelOptions): OtelHandle {\n if (activeHandle) {\n return activeHandle;\n }\n\n const tracesEndpoint = resolveTracesEndpoint(options.endpoint);\n const logsEndpoint = resolveLogsEndpoint(options.endpoint);\n const mergedHeaders = {\n ...options.headers,\n ...parseEnvHeaders(process.env.OTEL_EXPORTER_OTLP_HEADERS),\n };\n const hasHeaders = Object.keys(mergedHeaders).length > 0;\n\n const resource = resourceFromAttributes({\n \"service.name\": options.serviceName,\n ...(options.serviceVersion\n ? { \"service.version\": options.serviceVersion }\n : {}),\n \"deployment.environment\": process.env.DEPLOYMENT_ENV ?? \"development\",\n ...options.resourceAttributes,\n });\n\n context.setGlobalContextManager(new AsyncLocalStorageContextManager());\n\n const traceProcessors = tracesEndpoint\n ? [\n new BatchSpanProcessor(\n new OTLPTraceExporter({\n url: tracesEndpoint,\n headers: hasHeaders ? mergedHeaders : undefined,\n })\n ),\n ]\n : [];\n\n const tracerProvider = new BasicTracerProvider({\n resource,\n spanProcessors: traceProcessors,\n });\n trace.setGlobalTracerProvider(tracerProvider);\n\n const logProcessors = logsEndpoint\n ? [\n new BatchLogRecordProcessor(\n new OTLPLogExporter({\n url: logsEndpoint,\n headers: hasHeaders ? mergedHeaders : undefined,\n })\n ),\n ]\n : [];\n\n const loggerProvider = new LoggerProvider({\n resource,\n processors: logProcessors,\n });\n logs.setGlobalLoggerProvider(loggerProvider);\n\n const handle: OtelHandle = {\n async shutdown() {\n await Promise.allSettled([\n tracerProvider.shutdown(),\n loggerProvider.shutdown(),\n ]);\n activeHandle = undefined;\n },\n };\n\n activeHandle = handle;\n return handle;\n}\n\n/**\n * Read-only accessor for tests / debug paths that need to know whether\n * `setupOtel` has already run in this process.\n */\nexport function isOtelActive(): boolean {\n return activeHandle !== undefined;\n}\n","import {\n type Attributes,\n SpanStatusCode,\n type Tracer,\n trace,\n} from \"@opentelemetry/api\";\nimport type { LogAttrs } from \"./logger\";\nimport { sanitizeErrorMessage } from \"./sanitize\";\nimport { PHOTON_OTEL_VERSION } from \"./version\";\n\nlet scopedTracer: Tracer | undefined;\n\nfunction getTracer(): Tracer {\n if (!scopedTracer) {\n scopedTracer = trace.getTracer(\"@photon-ai/otel\", PHOTON_OTEL_VERSION);\n }\n return scopedTracer;\n}\n\nfunction toAttributes(attrs: LogAttrs): Attributes {\n const out: Attributes = {};\n for (const [k, v] of Object.entries(attrs)) {\n if (v !== undefined) {\n out[k] = v;\n }\n }\n return out;\n}\n\nexport function withSpan<T>(name: string, fn: () => Promise<T> | T): Promise<T>;\nexport function withSpan<T>(\n name: string,\n attrs: LogAttrs,\n fn: () => Promise<T> | T\n): Promise<T>;\nexport function withSpan<T>(\n name: string,\n attrsOrFn: LogAttrs | (() => Promise<T> | T),\n maybeFn?: () => Promise<T> | T\n): Promise<T> {\n const fn = typeof attrsOrFn === \"function\" ? attrsOrFn : maybeFn;\n if (!fn) {\n throw new Error(\"withSpan: function argument is required\");\n }\n const attrs = typeof attrsOrFn === \"function\" ? undefined : attrsOrFn;\n\n return getTracer().startActiveSpan(name, async (span) => {\n if (attrs) {\n span.setAttributes(toAttributes(attrs));\n }\n try {\n const result = await fn();\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (err) {\n span.recordException(err as Error);\n const errorObj = err instanceof Error ? err : undefined;\n span.setAttribute(\"error.type\", errorObj?.constructor.name ?? typeof err);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: errorObj\n ? sanitizeErrorMessage(errorObj.message)\n : sanitizeErrorMessage(String(err)),\n });\n throw err;\n } finally {\n span.end();\n }\n });\n}\n"],"mappings":"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","names":["logs","trace","trace"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@photon-ai/otel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DX-focused OpenTelemetry wrapper for Bun and Node.js: one-call setup, structured logging, span helper, and built-in PII scrubbing.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"bun": "./src/index.ts",
|
|
16
|
+
"types": "./src/index.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"check": "ultracite check",
|
|
33
|
+
"fix": "ultracite fix",
|
|
34
|
+
"prepublishOnly": "bun run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@opentelemetry/api": "^1.9.1",
|
|
38
|
+
"@opentelemetry/api-logs": "^0.216.0",
|
|
39
|
+
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
40
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.216.0",
|
|
41
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.216.0",
|
|
42
|
+
"@opentelemetry/resources": "^2.7.1",
|
|
43
|
+
"@opentelemetry/sdk-logs": "^0.216.0",
|
|
44
|
+
"@opentelemetry/sdk-trace-base": "^2.7.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@biomejs/biome": "2.4.15",
|
|
48
|
+
"@types/bun": "latest",
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"tsup": "^8.4.0",
|
|
51
|
+
"ultracite": "7.7.0",
|
|
52
|
+
"vitest": "^2.1.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"typescript": "^5 || ^6.0.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20"
|
|
59
|
+
},
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"keywords": [
|
|
62
|
+
"opentelemetry",
|
|
63
|
+
"otel",
|
|
64
|
+
"tracing",
|
|
65
|
+
"logging",
|
|
66
|
+
"observability",
|
|
67
|
+
"bun",
|
|
68
|
+
"nodejs",
|
|
69
|
+
"otlp"
|
|
70
|
+
]
|
|
71
|
+
}
|