@afora/otel 0.0.0-stage → 0.0.10
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 +57 -2
- package/dist/index.d.ts +19 -0
- package/dist/index.js +40 -0
- package/package.json +60 -5
package/README.md
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @afora/otel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[OpenTelemetry](https://opentelemetry.io) tracing for [`@afora/core`](../../README.md) applications: a span per command operation, use case and repository call made through a use case, nested as the calls are, with the exception layer and name on failure and no input, response or sensitive value in any span. What the spans are and carry is in [docs/tracing.md](../../docs/tracing.md).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun add @afora/core @afora/otel @opentelemetry/api
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`@afora/core` and `@opentelemetry/api` (`^1.9.0`) are peer dependencies. Set up an OpenTelemetry SDK as usual (`@opentelemetry/sdk-node`, an exporter); the package itself uses only the API, so it runs on Bun, Node >= 22 and Workers.
|
|
10
|
+
|
|
11
|
+
## Example
|
|
12
|
+
|
|
13
|
+
<!-- docs:code skip: Post, Posts, Blog and Api are the declarations of your application -->
|
|
14
|
+
```ts
|
|
15
|
+
import { applicationOf } from "@afora/core";
|
|
16
|
+
import { otelTracer } from "@afora/otel";
|
|
17
|
+
|
|
18
|
+
export const App = applicationOf({
|
|
19
|
+
name: "blog",
|
|
20
|
+
entities: { Post },
|
|
21
|
+
repositories: { Posts },
|
|
22
|
+
commands: { Blog },
|
|
23
|
+
outputs: { Api },
|
|
24
|
+
trace: otelTracer(),
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A request to `App.outputs.Api.build()`, on Hono or any HTTP sink, gives one trace:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
command Blog.publish afora.command=Blog.publish afora.operation=publish afora.outcome=ok
|
|
32
|
+
└─ use-case Publish afora.use_case=Publish afora.outcome=ok
|
|
33
|
+
└─ repository Post.create afora.repository=Post afora.method=create afora.use_case=Publish
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
- **`otelTracer(tracer?)`**: the `ITracer` of `@afora/core` over an OpenTelemetry `Tracer`, by default `trace.getTracer(AFORA_TRACER_NAME)` of the global tracer provider (it works before the SDK registers, as OpenTelemetry tracers do).
|
|
39
|
+
- A command span is a child of the active span, such as the server span of your HTTP instrumentation; the spans below it are children of their caller's span whether or not a context manager is registered.
|
|
40
|
+
- With a context manager, each call runs with its span active, so the spans your code and instrumented drivers start inside a use case or a repository call nest in it.
|
|
41
|
+
- A failed span has the status `ERROR`, with the exception name as its message, and the attributes `afora.exception.layer`, `afora.exception.name` and `afora.exception.unexpected`; no exception event is recorded, since the message and stack of an error can hold values. A span that succeeded keeps the status unset.
|
|
42
|
+
- **`AFORA_TRACER_NAME`**: `"@afora/otel"`, the instrumentation scope of the default tracer.
|
|
43
|
+
|
|
44
|
+
## In an afora.json
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{ "version": 1, "name": "blog", "trace": { "import": "../trace.ts#tracer" } }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
with `export const tracer = otelTracer();` in `trace.ts` (see [afora.json](../../docs/afora-json.md#tracing)).
|
|
51
|
+
|
|
52
|
+
## Tests
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bun run test:otel # from the repository root
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The tests serve an application through [`@afora/hono`](../hono/README.md) with an in-memory span exporter, and check the trace of a request (command, use cases, repository calls), the attributes of a failure, and that no password, hash, input or id reaches any span.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ITracer } from "@afora/core";
|
|
2
|
+
import { type Tracer } from "@opentelemetry/api";
|
|
3
|
+
/** The instrumentation scope of the default tracer. */
|
|
4
|
+
export declare const AFORA_TRACER_NAME = "@afora/otel";
|
|
5
|
+
/**
|
|
6
|
+
* The `trace` of an application, over an OpenTelemetry `Tracer` (by default
|
|
7
|
+
* `trace.getTracer("@afora/otel")`, of the global tracer provider):
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* applicationOf({ name: "blog", trace: otelTracer(), ... });
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* A command span is a child of the active span (an HTTP server span of your
|
|
14
|
+
* instrumentation, say); a use case span a child of its command or of the use
|
|
15
|
+
* case that ran it; a repository span a child of its use case, whether or not
|
|
16
|
+
* a context manager is registered. With one, each call also runs with its
|
|
17
|
+
* span active, so the spans of your code and of instrumented drivers nest in it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function otelTracer(tracer?: Tracer): ITracer;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
context,
|
|
4
|
+
SpanStatusCode,
|
|
5
|
+
trace
|
|
6
|
+
} from "@opentelemetry/api";
|
|
7
|
+
var AFORA_TRACER_NAME = "@afora/otel";
|
|
8
|
+
|
|
9
|
+
class OtelSpan {
|
|
10
|
+
span;
|
|
11
|
+
constructor(span) {
|
|
12
|
+
this.span = span;
|
|
13
|
+
}
|
|
14
|
+
end(status, attributes) {
|
|
15
|
+
this.span.setAttributes(attributes);
|
|
16
|
+
if (status === "error") {
|
|
17
|
+
const name = attributes["afora.exception.name"];
|
|
18
|
+
this.span.setStatus({
|
|
19
|
+
code: SpanStatusCode.ERROR,
|
|
20
|
+
...typeof name === "string" ? { message: name } : {}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
this.span.end();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function otelTracer(tracer = trace.getTracer(AFORA_TRACER_NAME)) {
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
startSpan(name, attributes, parent) {
|
|
29
|
+
const active = context.active();
|
|
30
|
+
return new OtelSpan(tracer.startSpan(name, { attributes }, parent instanceof OtelSpan ? trace.setSpan(active, parent.span) : active));
|
|
31
|
+
},
|
|
32
|
+
withSpan(span, run) {
|
|
33
|
+
return span instanceof OtelSpan ? context.with(trace.setSpan(context.active(), span.span), run) : run();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
AFORA_TRACER_NAME,
|
|
39
|
+
otelTracer
|
|
40
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@afora/otel",
|
|
3
|
+
"version": "0.0.10",
|
|
4
|
+
"description": "OpenTelemetry tracing for afora applications: a span per command, use case and repository call, nested as the calls are, with no input, response or sensitive value in any span.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"afora",
|
|
7
|
+
"opentelemetry",
|
|
8
|
+
"otel",
|
|
9
|
+
"tracing",
|
|
10
|
+
"observability"
|
|
11
|
+
],
|
|
12
|
+
"author": "Alan Reis <alanreisanjo@gmail.com> (https://github.com/Hoyasumii)",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"homepage": "https://github.com/afora-framework/core/tree/main/packages/otel#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/afora-framework/core.git",
|
|
18
|
+
"directory": "packages/otel"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"bun": ">=1.4.4",
|
|
37
|
+
"node": ">=22"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"provenance": true
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"typecheck": "bun check",
|
|
45
|
+
"build": "rm -rf dist && bun build ./src/index.ts --outdir ./dist --target node --format esm --packages external --tsconfig-override tsconfig.build.json && tsc -p tsconfig.build.json && bun ../../scripts/add-dts-extensions.ts dist",
|
|
46
|
+
"test": "bun test"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@afora/hono": "workspace:*",
|
|
50
|
+
"@opentelemetry/api": "^1.9.0",
|
|
51
|
+
"@opentelemetry/context-async-hooks": "^2.2.0",
|
|
52
|
+
"@opentelemetry/sdk-trace-base": "^2.2.0",
|
|
53
|
+
"@types/bun": "^1.4.4",
|
|
54
|
+
"hono": "^4.13.14",
|
|
55
|
+
"typebox": "^1.4.3"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@afora/core": ">=0.0.11",
|
|
59
|
+
"@opentelemetry/api": "^1.9.0"
|
|
60
|
+
}
|
|
61
|
+
}
|