/ opencodehub / src / lib / logger.ts
swadhinbiswas # Changelog · 1/17/2026
9eabb75
112 lines · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import pino from "pino";

// Custom OTLP transport for Grafana Cloud
const createOtlpTransport = () => {
  const endpoint = process.env.GRAFANA_OTLP_ENDPOINT;
  const instanceId = process.env.GRAFANA_INSTANCE_ID;
  const apiKey = process.env.GRAFANA_API_KEY;

  if (!endpoint || !instanceId || !apiKey) {
    return null;
  }

  const authPair = `${instanceId}:${apiKey}`;
  const encoded = Buffer.from(authPair).toString('base64');

  return {
    send: async (level: string, message: string, extra: Record<string, unknown> = {}) => {
      try {
        const body = {
          resourceLogs: [{
            resource: {
              attributes: [
                { key: "service.name", value: { stringValue: "opencodehub" } },
                { key: "environment", value: { stringValue: process.env.NODE_ENV || "development" } }
              ]
            },
            scopeLogs: [{
              logRecords: [{
                timeUnixNano: String(Date.now() * 1e6),
                severityText: level.toUpperCase(),
                body: { stringValue: message },
                attributes: Object.entries(extra).map(([key, value]) => ({
                  key,
                  value: { stringValue: String(value) }
                }))
              }]
            }]
          }]
        };

        await fetch(endpoint, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Basic ${encoded}`,
          },
          body: JSON.stringify(body)
        });
      } catch (error) {
        // Silently fail to avoid infinite logging loops
        console.error('[OTLP Transport Error]', error);
      }
    }
  };
};

const transports = [];

// Always add standard output (pretty in dev, json in prod)
if (process.env.NODE_ENV === "development") {
  transports.push({
    target: "pino-pretty",
    options: {
      colorize: true,
    },
  });
} else {
  transports.push({
    target: "pino/file",
    options: { destination: 1 }
  });
}

// Initialize OTLP transport
const otlpTransport = createOtlpTransport();

// Create base pino logger
const baseLogger = pino({
  level: process.env.LOG_LEVEL || "info",
  transport: transports.length > 0 ? {
    targets: transports
  } : undefined,
});

// Wrap logger to also send to OTLP if configured
const createWrappedLogger = () => {
  const wrap = (level: string) => (msg: string | object, ...args: unknown[]) => {
    // Call original pino method
    (baseLogger as unknown as Record<string, Function>)[level](msg, ...args);

    // Send to OTLP if configured
    if (otlpTransport) {
      const message = typeof msg === 'string' ? msg : JSON.stringify(msg);
      const extra = typeof msg === 'object' ? msg : (args[0] as Record<string, unknown>) || {};
      otlpTransport.send(level, message, extra as Record<string, unknown>);
    }
  };

  return {
    trace: wrap('trace'),
    debug: wrap('debug'),
    info: wrap('info'),
    warn: wrap('warn'),
    error: wrap('error'),
    fatal: wrap('fatal'),
    child: baseLogger.child.bind(baseLogger),
    level: baseLogger.level,
  };
};

export const logger = otlpTransport ? createWrappedLogger() : baseLogger;