kensa-sdk 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/LICENSE +21 -0
- package/README.md +80 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +123 -0
- package/dist/index.js.map +1 -0
- package/dist/resource.d.ts +13 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +65 -0
- package/dist/resource.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +25 -0
- package/dist/version.js.map +1 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Borgware
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# kensa-sdk
|
|
2
|
+
|
|
3
|
+
Send your agent's OpenTelemetry traces to [Kensa](https://kensa.sh).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install kensa-sdk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { kensa } from "kensa-sdk";
|
|
13
|
+
|
|
14
|
+
kensa.init({ apiKey: process.env.KENSA_API_KEY });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Spans are sent in the background and flushed when the process exits normally (not on
|
|
18
|
+
`process.exit()`). In a serverless
|
|
19
|
+
function, flush before the response ends:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
await kensa.flush();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`kensa.init()` sends spans; your existing instrumentation, such as the AI SDK's
|
|
26
|
+
OpenTelemetry integration, produces them. Without an API key it does nothing and logs
|
|
27
|
+
a warning, and nothing the SDK does can throw into your application.
|
|
28
|
+
|
|
29
|
+
## Already using OpenTelemetry?
|
|
30
|
+
|
|
31
|
+
If your app registers its own tracer provider, for example with `@vercel/otel`, add
|
|
32
|
+
Kensa's span processor to it instead of calling `kensa.init()`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { registerOTel } from "@vercel/otel";
|
|
36
|
+
import { kensa } from "kensa-sdk";
|
|
37
|
+
|
|
38
|
+
registerOTel({
|
|
39
|
+
serviceName: "support-agent",
|
|
40
|
+
// @vercel/otel sets service.version to the deployment ID; Kensa matches fixes on the commit.
|
|
41
|
+
attributes: { "service.version": process.env.VERCEL_GIT_COMMIT_SHA },
|
|
42
|
+
spanProcessors: [kensa.spanProcessor({ apiKey: process.env.KENSA_API_KEY })],
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`@vercel/otel` already sets `deployment.environment.name` from `VERCEL_ENV`. Deployment
|
|
47
|
+
detection only runs in `kensa.init()`, so with any other provider set `service.version`
|
|
48
|
+
and `deployment.environment.name` on its resource from your platform's variables
|
|
49
|
+
(table below).
|
|
50
|
+
|
|
51
|
+
If you do call `kensa.init()`, call it after any code that registers a tracer provider:
|
|
52
|
+
once Kensa's provider is registered, OpenTelemetry refuses to replace it.
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
| Setting | Set with | Default |
|
|
57
|
+
| ------------ | ------------------------------------------------ | --------------------------------- |
|
|
58
|
+
| API key | `init({ apiKey })` or `KENSA_API_KEY` | Required |
|
|
59
|
+
| Service name | `init({ serviceName })` or `OTEL_SERVICE_NAME` | Package name under npm scripts, else `unknown_service:node` |
|
|
60
|
+
| Deployment | `OTEL_RESOURCE_ATTRIBUTES` | Detected from your platform |
|
|
61
|
+
|
|
62
|
+
Kensa checks a fix against the traces from the deployment that shipped it, so it needs
|
|
63
|
+
the deployed commit and the environment. `kensa.init()` detects them:
|
|
64
|
+
|
|
65
|
+
| Platform | Commit | Environment |
|
|
66
|
+
| ---------------- | ------------------------ | -------------------------- |
|
|
67
|
+
| Vercel | `VERCEL_GIT_COMMIT_SHA` | `VERCEL_ENV` |
|
|
68
|
+
| Railway | `RAILWAY_GIT_COMMIT_SHA` | `RAILWAY_ENVIRONMENT_NAME` |
|
|
69
|
+
| Render | `RENDER_GIT_COMMIT` | |
|
|
70
|
+
|
|
71
|
+
On Render, set the environment yourself (below). CI and local runs are never detected
|
|
72
|
+
as a deployment.
|
|
73
|
+
|
|
74
|
+
Anywhere else, or to override detection, set the standard OpenTelemetry variable:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
OTEL_RESOURCE_ATTRIBUTES="service.version=<commit sha>,deployment.environment.name=production"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Requires Node.js 20 or later.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type SpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
2
|
+
export type InitOptions = {
|
|
3
|
+
/** Defaults to `KENSA_API_KEY`. */
|
|
4
|
+
apiKey?: string | undefined;
|
|
5
|
+
/** Defaults to `OTEL_SERVICE_NAME`, then the package name. */
|
|
6
|
+
serviceName?: string | undefined;
|
|
7
|
+
};
|
|
8
|
+
export type SpanProcessorOptions = {
|
|
9
|
+
/** Defaults to `KENSA_API_KEY`. */
|
|
10
|
+
apiKey?: string | undefined;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* A span processor that sends spans to Kensa, for applications that set up their own
|
|
14
|
+
* tracer provider, for example with `registerOTel()` from `@vercel/otel`.
|
|
15
|
+
*/
|
|
16
|
+
declare function spanProcessor(options?: SpanProcessorOptions): SpanProcessor;
|
|
17
|
+
/**
|
|
18
|
+
* Start sending this process's traces to Kensa. Only the first call with an API key
|
|
19
|
+
* takes effect. Call it after any code that registers a tracer provider: if one is
|
|
20
|
+
* already registered, this logs how to add `kensa.spanProcessor()` to it instead.
|
|
21
|
+
*/
|
|
22
|
+
declare function init(options?: InitOptions): void;
|
|
23
|
+
/**
|
|
24
|
+
* Send any buffered spans now. Pending spans are flushed automatically when the process
|
|
25
|
+
* exits normally; call this at the end of a serverless request. Never throws: a failed
|
|
26
|
+
* send is logged, so Kensa being unreachable cannot break the application.
|
|
27
|
+
*/
|
|
28
|
+
declare function flush(): Promise<void>;
|
|
29
|
+
export declare const kensa: {
|
|
30
|
+
init: typeof init;
|
|
31
|
+
flush: typeof flush;
|
|
32
|
+
spanProcessor: typeof spanProcessor;
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,+BAA+B,CAAC;AASvC,MAAM,MAAM,WAAW,GAAG;IACxB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AA4CF;;;GAGG;AACH,iBAAS,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CA4BxE;AAED;;;;GAIG;AACH,iBAAS,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,IAAI,CA4B7C;AAED;;;;GAIG;AACH,iBAAe,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAOpC;AAED,eAAO,MAAM,KAAK;;;;CAAiC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { ProxyTracerProvider, trace } from "@opentelemetry/api";
|
|
2
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
3
|
+
import { BatchSpanProcessor, NodeTracerProvider, NoopSpanProcessor, } from "@opentelemetry/sdk-trace-node";
|
|
4
|
+
import { resourceFor } from "./resource.js";
|
|
5
|
+
const DEFAULT_ENDPOINT = "https://ingest.kensa.sh/v1/traces";
|
|
6
|
+
// Ingest accepts at most 4 MiB per request and GenAI spans carry whole prompts, so
|
|
7
|
+
// batches stay far below the exporter's default of 512 spans.
|
|
8
|
+
const MAX_EXPORT_BATCH_SIZE = 24;
|
|
9
|
+
const processors = new Set();
|
|
10
|
+
let initialized = false;
|
|
11
|
+
let warnedMissingKey = false;
|
|
12
|
+
function warnMissingKey() {
|
|
13
|
+
if (warnedMissingKey)
|
|
14
|
+
return;
|
|
15
|
+
warnedMissingKey = true;
|
|
16
|
+
console.warn("[kensa] KENSA_API_KEY is not set, so no traces are sent to Kensa.");
|
|
17
|
+
}
|
|
18
|
+
// The OTLP exporter reads OTEL_EXPORTER_OTLP_* (headers, certificates) when it is
|
|
19
|
+
// constructed and merges them under explicit options. Those belong to whatever other
|
|
20
|
+
// backend the application exports to, so hide them while Kensa's exporter is built.
|
|
21
|
+
// Construction is synchronous, so nothing else can observe the change.
|
|
22
|
+
function withoutOtlpEnvironment(build) {
|
|
23
|
+
const hidden = Object.entries(process.env).filter(([name]) => name.startsWith("OTEL_EXPORTER_OTLP_"));
|
|
24
|
+
for (const [name] of hidden)
|
|
25
|
+
delete process.env[name];
|
|
26
|
+
try {
|
|
27
|
+
return build();
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
for (const [name, value] of hidden)
|
|
31
|
+
process.env[name] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const PROVIDER_CONFLICT = "[kensa] A tracer provider is already registered, so kensa.init() cannot add its " +
|
|
35
|
+
"own. Pass kensa.spanProcessor() to that provider's spanProcessors instead.";
|
|
36
|
+
function globalProvider() {
|
|
37
|
+
const current = trace.getTracerProvider();
|
|
38
|
+
return current instanceof ProxyTracerProvider ? current.getDelegate() : current;
|
|
39
|
+
}
|
|
40
|
+
// An SDK provider can flush; the API's default no-op provider cannot.
|
|
41
|
+
function hasRegisteredProvider() {
|
|
42
|
+
return (typeof globalProvider().forceFlush === "function");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A span processor that sends spans to Kensa, for applications that set up their own
|
|
46
|
+
* tracer provider, for example with `registerOTel()` from `@vercel/otel`.
|
|
47
|
+
*/
|
|
48
|
+
function spanProcessor(options = {}) {
|
|
49
|
+
const apiKey = options.apiKey || process.env.KENSA_API_KEY;
|
|
50
|
+
if (!apiKey) {
|
|
51
|
+
warnMissingKey();
|
|
52
|
+
return new NoopSpanProcessor();
|
|
53
|
+
}
|
|
54
|
+
let processor;
|
|
55
|
+
try {
|
|
56
|
+
const exporter = withoutOtlpEnvironment(() => new OTLPTraceExporter({
|
|
57
|
+
url: process.env.KENSA_ENDPOINT || DEFAULT_ENDPOINT,
|
|
58
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
59
|
+
// Ingest redacts prompt-rich batches before acknowledging them.
|
|
60
|
+
timeoutMillis: 120_000,
|
|
61
|
+
}));
|
|
62
|
+
processor = new BatchSpanProcessor(exporter, {
|
|
63
|
+
maxExportBatchSize: MAX_EXPORT_BATCH_SIZE,
|
|
64
|
+
exportTimeoutMillis: 120_000,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.warn(`[kensa] Not sending traces: ${String(error)}`);
|
|
69
|
+
return new NoopSpanProcessor();
|
|
70
|
+
}
|
|
71
|
+
if (processors.size === 0)
|
|
72
|
+
process.once("beforeExit", () => void flush());
|
|
73
|
+
processors.add(processor);
|
|
74
|
+
return processor;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Start sending this process's traces to Kensa. Only the first call with an API key
|
|
78
|
+
* takes effect. Call it after any code that registers a tracer provider: if one is
|
|
79
|
+
* already registered, this logs how to add `kensa.spanProcessor()` to it instead.
|
|
80
|
+
*/
|
|
81
|
+
function init(options = {}) {
|
|
82
|
+
if (initialized)
|
|
83
|
+
return;
|
|
84
|
+
const apiKey = options.apiKey || process.env.KENSA_API_KEY;
|
|
85
|
+
if (!apiKey) {
|
|
86
|
+
warnMissingKey();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
initialized = true;
|
|
90
|
+
if (hasRegisteredProvider()) {
|
|
91
|
+
console.error(PROVIDER_CONFLICT);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const provider = new NodeTracerProvider({
|
|
96
|
+
resource: resourceFor(options.serviceName, process.env),
|
|
97
|
+
spanProcessors: [spanProcessor({ apiKey })],
|
|
98
|
+
});
|
|
99
|
+
provider.register();
|
|
100
|
+
// A provider without forceFlush (another vendor's) still holds the global slot.
|
|
101
|
+
if (globalProvider() !== provider) {
|
|
102
|
+
initialized = false;
|
|
103
|
+
void provider.shutdown();
|
|
104
|
+
console.error(PROVIDER_CONFLICT);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.warn(`[kensa] Not sending traces: ${String(error)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Send any buffered spans now. Pending spans are flushed automatically when the process
|
|
113
|
+
* exits normally; call this at the end of a serverless request. Never throws: a failed
|
|
114
|
+
* send is logged, so Kensa being unreachable cannot break the application.
|
|
115
|
+
*/
|
|
116
|
+
async function flush() {
|
|
117
|
+
const results = await Promise.allSettled([...processors].map((processor) => processor.forceFlush()));
|
|
118
|
+
if (results.some((result) => result.status === "rejected")) {
|
|
119
|
+
console.warn("[kensa] Some spans could not be sent to Kensa.");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export const kensa = { init, flush, spanProcessor };
|
|
123
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Resource } from "@opentelemetry/resources";
|
|
2
|
+
type Environment = Record<string, string | undefined>;
|
|
3
|
+
/** The deployment Kensa can infer from the platform: commit, environment, service name. */
|
|
4
|
+
export declare function detectedAttributes(env: Environment): Record<string, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Detected values, overridden by the standard OTEL_SERVICE_NAME and
|
|
7
|
+
* OTEL_RESOURCE_ATTRIBUTES, overridden by an explicit service name. Starts from
|
|
8
|
+
* OpenTelemetry's defaults because a provider given a resource does not add them, and
|
|
9
|
+
* Kensa ignores the deployment of spans that have no service.name.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resourceFor(serviceName: string | undefined, env: Environment): Resource;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAIlC,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AA0BtD,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAe3E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,GAAG,EAAE,WAAW,GACf,QAAQ,CAkBV"}
|
package/dist/resource.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { defaultResource, detectResources, envDetector, resourceFromAttributes, } from "@opentelemetry/resources";
|
|
2
|
+
import { packageVersion } from "./version.js";
|
|
3
|
+
// Full commit SHAs that hosting platforms expose at runtime. Kensa matches a reported
|
|
4
|
+
// fix's commit against the service.version of later traces. Deploy platforms only:
|
|
5
|
+
// CI and local runs must never pass for production traffic.
|
|
6
|
+
const COMMIT_VARIABLES = [
|
|
7
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
8
|
+
"RENDER_GIT_COMMIT",
|
|
9
|
+
"RAILWAY_GIT_COMMIT_SHA",
|
|
10
|
+
];
|
|
11
|
+
const ENVIRONMENT_VARIABLES = ["VERCEL_ENV", "RAILWAY_ENVIRONMENT_NAME"];
|
|
12
|
+
const ENVIRONMENT_KEYS = [
|
|
13
|
+
"deployment.environment.name",
|
|
14
|
+
"deployment.environment",
|
|
15
|
+
];
|
|
16
|
+
function first(env, names) {
|
|
17
|
+
for (const name of names) {
|
|
18
|
+
const value = env[name]?.trim();
|
|
19
|
+
if (value)
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
/** The deployment Kensa can infer from the platform: commit, environment, service name. */
|
|
25
|
+
export function detectedAttributes(env) {
|
|
26
|
+
const commit = first(env, COMMIT_VARIABLES);
|
|
27
|
+
const environment = first(env, ENVIRONMENT_VARIABLES);
|
|
28
|
+
const name = env.npm_package_name?.trim();
|
|
29
|
+
return {
|
|
30
|
+
...(name ? { "service.name": name } : {}),
|
|
31
|
+
...(commit ? { "service.version": commit } : {}),
|
|
32
|
+
// Both spellings: semantic conventions renamed the attribute, and Kensa prefers the new one.
|
|
33
|
+
...(environment
|
|
34
|
+
? {
|
|
35
|
+
"deployment.environment.name": environment,
|
|
36
|
+
"deployment.environment": environment,
|
|
37
|
+
}
|
|
38
|
+
: {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Detected values, overridden by the standard OTEL_SERVICE_NAME and
|
|
43
|
+
* OTEL_RESOURCE_ATTRIBUTES, overridden by an explicit service name. Starts from
|
|
44
|
+
* OpenTelemetry's defaults because a provider given a resource does not add them, and
|
|
45
|
+
* Kensa ignores the deployment of spans that have no service.name.
|
|
46
|
+
*/
|
|
47
|
+
export function resourceFor(serviceName, env) {
|
|
48
|
+
const version = packageVersion();
|
|
49
|
+
const fromEnv = detectResources({ detectors: [envDetector] });
|
|
50
|
+
const detected = detectedAttributes(env);
|
|
51
|
+
// An environment set through OTEL_RESOURCE_ATTRIBUTES, in either spelling, replaces
|
|
52
|
+
// the detected one entirely rather than leaving the two spellings disagreeing.
|
|
53
|
+
if (ENVIRONMENT_KEYS.some((key) => fromEnv.attributes[key] !== undefined)) {
|
|
54
|
+
for (const key of ENVIRONMENT_KEYS)
|
|
55
|
+
delete detected[key];
|
|
56
|
+
}
|
|
57
|
+
return defaultResource()
|
|
58
|
+
.merge(resourceFromAttributes(detected))
|
|
59
|
+
.merge(fromEnv)
|
|
60
|
+
.merge(resourceFromAttributes({
|
|
61
|
+
...(serviceName ? { "service.name": serviceName } : {}),
|
|
62
|
+
...(version ? { "kensa.instrumentation.version": version } : {}),
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,eAAe,EACf,WAAW,EACX,sBAAsB,GAEvB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAI9C,sFAAsF;AACtF,mFAAmF;AACnF,4DAA4D;AAC5D,MAAM,gBAAgB,GAAG;IACvB,uBAAuB;IACvB,mBAAmB;IACnB,wBAAwB;CAChB,CAAC;AAEX,MAAM,qBAAqB,GAAG,CAAC,YAAY,EAAE,0BAA0B,CAAU,CAAC;AAElF,MAAM,gBAAgB,GAAG;IACvB,6BAA6B;IAC7B,wBAAwB;CAChB,CAAC;AAEX,SAAS,KAAK,CAAC,GAAgB,EAAE,KAAwB;IACvD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;QAChC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,kBAAkB,CAAC,GAAgB;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAC5C,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,CAAC;IAC1C,OAAO;QACL,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,6FAA6F;QAC7F,GAAG,CAAC,WAAW;YACb,CAAC,CAAC;gBACE,6BAA6B,EAAE,WAAW;gBAC1C,wBAAwB,EAAE,WAAW;aACtC;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,WAA+B,EAC/B,GAAgB;IAEhB,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACzC,oFAAoF;IACpF,+EAA+E;IAC/E,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,EAAE,CAAC;QAC1E,KAAK,MAAM,GAAG,IAAI,gBAAgB;YAAE,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,eAAe,EAAE;SACrB,KAAK,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC;SACvC,KAAK,CAAC,OAAO,CAAC;SACd,KAAK,CACJ,sBAAsB,CAAC;QACrB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,+BAA+B,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACjE,CAAC,CACH,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This package's version, read from its package.json so the manifest is the only place
|
|
3
|
+
* it is written. Undefined when a bundler dropped that file; the name check stops a
|
|
4
|
+
* bundle from picking up the host application's package.json instead.
|
|
5
|
+
*/
|
|
6
|
+
export declare function packageVersion(): string | undefined;
|
|
7
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAiBnD"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const PACKAGE_NAME = "kensa-sdk";
|
|
3
|
+
/**
|
|
4
|
+
* This package's version, read from its package.json so the manifest is the only place
|
|
5
|
+
* it is written. Undefined when a bundler dropped that file; the name check stops a
|
|
6
|
+
* bundle from picking up the host application's package.json instead.
|
|
7
|
+
*/
|
|
8
|
+
export function packageVersion() {
|
|
9
|
+
try {
|
|
10
|
+
const manifest = createRequire(import.meta.url)("../package.json");
|
|
11
|
+
if (typeof manifest === "object" &&
|
|
12
|
+
manifest !== null &&
|
|
13
|
+
"name" in manifest &&
|
|
14
|
+
manifest.name === PACKAGE_NAME &&
|
|
15
|
+
"version" in manifest &&
|
|
16
|
+
typeof manifest.version === "string") {
|
|
17
|
+
return manifest.version;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Fall through: an unknown version must never stop instrumentation.
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,YAAY,GAAG,WAAW,CAAC;AAEjC;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAY,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAC5E,IACE,OAAO,QAAQ,KAAK,QAAQ;YAC5B,QAAQ,KAAK,IAAI;YACjB,MAAM,IAAI,QAAQ;YAClB,QAAQ,CAAC,IAAI,KAAK,YAAY;YAC9B,SAAS,IAAI,QAAQ;YACrB,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,QAAQ,CAAC,OAAO,CAAC;QAC1B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oEAAoE;IACtE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kensa-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Thin OpenTelemetry wrapper that exports agent traces to Kensa",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://kensa.sh",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc --project tsconfig.build.json",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"prepublishOnly": "pnpm build"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@opentelemetry/api": "^1.9.0",
|
|
33
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.208.0",
|
|
34
|
+
"@opentelemetry/resources": "^2.2.0",
|
|
35
|
+
"@opentelemetry/sdk-trace-node": "^2.2.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^26.4.0",
|
|
39
|
+
"typescript": "^6.0.3",
|
|
40
|
+
"vitest": "^4.1.11"
|
|
41
|
+
}
|
|
42
|
+
}
|