otlp.ts 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import { Layer } from "effect"
  2. import { OtlpLogger } from "effect/unstable/observability"
  3. import { Flag } from "../flag/flag"
  4. import { InstallationChannel, InstallationVersion } from "../installation/version"
  5. import { runID } from "./shared"
  6. const endpoint = Flag.OTEL_EXPORTER_OTLP_ENDPOINT
  7. const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
  8. ? Flag.OTEL_EXPORTER_OTLP_HEADERS.split(",").reduce(
  9. (acc, entry) => {
  10. const [key, ...value] = entry.split("=")
  11. acc[key] = value.join("=")
  12. return acc
  13. },
  14. {} as Record<string, string>,
  15. )
  16. : undefined
  17. function resourceAttributes() {
  18. const value = process.env.OTEL_RESOURCE_ATTRIBUTES
  19. if (!value) return {}
  20. try {
  21. return Object.fromEntries(
  22. value.split(",").map((entry) => {
  23. const index = entry.indexOf("=")
  24. if (index < 1) throw new Error("Invalid OTEL_RESOURCE_ATTRIBUTES entry")
  25. return [decodeURIComponent(entry.slice(0, index)), decodeURIComponent(entry.slice(index + 1))]
  26. }),
  27. )
  28. } catch {
  29. return {}
  30. }
  31. }
  32. export function resource(): { serviceName: string; serviceVersion: string; attributes: Record<string, string> } {
  33. return {
  34. serviceName: "kirincode",
  35. serviceVersion: InstallationVersion,
  36. attributes: {
  37. ...resourceAttributes(),
  38. "deployment.environment.name": InstallationChannel,
  39. "kirincode.client": Flag.KIRINCODE_CLIENT,
  40. "kirincode.run": runID,
  41. "service.instance.id": runID,
  42. },
  43. }
  44. }
  45. export function loggers() {
  46. if (!endpoint) return []
  47. return [OtlpLogger.make({ url: `${endpoint}/v1/logs`, resource: resource(), headers })]
  48. }
  49. export async function tracingLayer() {
  50. if (!endpoint) return Layer.empty
  51. const NodeSdk = await import("@effect/opentelemetry/NodeSdk")
  52. const OTLP = await import("@opentelemetry/exporter-trace-otlp-http")
  53. const SdkBase = await import("@opentelemetry/sdk-trace-base")
  54. const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks")
  55. const { context } = await import("@opentelemetry/api")
  56. // The Effect Node SDK does not register a global context manager, but the AI SDK uses it to parent spans.
  57. const manager = new AsyncLocalStorageContextManager()
  58. manager.enable()
  59. context.setGlobalContextManager(manager)
  60. return NodeSdk.layer(() => ({
  61. resource: resource(),
  62. spanProcessor: new SdkBase.BatchSpanProcessor(
  63. new OTLP.OTLPTraceExporter({
  64. url: `${endpoint}/v1/traces`,
  65. headers,
  66. }),
  67. ),
  68. }))
  69. }
  70. export * as Otlp from "./otlp"