Skip to content

Commit e31e66a

Browse files
authored
feat(js): Support Otel logging (#4981)
1 parent 43d719c commit e31e66a

18 files changed

Lines changed: 1087 additions & 31 deletions

File tree

genkit-tools/cli/src/utils/manager-utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import {
18+
LocalFileLogStore,
1819
LocalFileTraceStore,
1920
startTelemetryServer,
2021
} from '@genkit-ai/telemetry-server';
@@ -48,6 +49,10 @@ export async function resolveTelemetryServer(options: {
4849
storeRoot: options.projectRoot,
4950
indexRoot: options.projectRoot,
5051
}),
52+
logStore: new LocalFileLogStore({
53+
storeRoot: options.projectRoot,
54+
indexRoot: options.projectRoot,
55+
}),
5156
corsOrigin: options.corsOrigin,
5257
});
5358
}

genkit-tools/common/src/manager/manager.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,40 @@ export abstract class BaseRuntimeManager {
135135
return apis.ListTracesResponseSchema.parse(response.data);
136136
}
137137

138+
/**
139+
* Retrieves all logs
140+
*/
141+
async listLogs(input: apis.ListLogsRequest): Promise<apis.ListLogsResponse> {
142+
const { limit, continuationToken, filter } = input;
143+
144+
let url = `${this.telemetryServerUrl}/api/logs`;
145+
if (filter?.traceId) {
146+
url = `${this.telemetryServerUrl}/api/traces/${filter.traceId}/logs`;
147+
if (filter.spanId) {
148+
url = `${this.telemetryServerUrl}/api/traces/${filter.traceId}/spans/${filter.spanId}/logs`;
149+
}
150+
}
151+
152+
let query = '';
153+
if (limit) {
154+
query += `limit=${limit}`;
155+
}
156+
if (continuationToken) {
157+
if (query !== '') query += '&';
158+
query += `continuationToken=${continuationToken}`;
159+
}
160+
161+
const fullUrl = query !== '' ? `${url}?${query}` : url;
162+
163+
const response = await axios
164+
.get(fullUrl)
165+
.catch((err) =>
166+
this.httpErrorHandler(err, `Error listing logs for url='${fullUrl}'.`)
167+
);
168+
169+
return apis.ListLogsResponseSchema.parse(response.data);
170+
}
171+
138172
/**
139173
* Retrieves a trace for a given ID.
140174
*/

genkit-tools/common/src/server/router.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ export const TOOLS_SERVER_ROUTER = (manager: BaseRuntimeManager) =>
160160
return manager.listTraces(input);
161161
}),
162162

163+
/** Retrieves all logs. */
164+
listLogs: loggedProcedure
165+
.input(apis.ListLogsRequestSchema)
166+
.query(async ({ input }) => {
167+
return manager.listLogs(input);
168+
}),
169+
163170
/** Retrieves a trace for a given ID. */
164171
getTrace: loggedProcedure
165172
.input(apis.GetTraceRequestSchema)

genkit-tools/common/src/types/apis.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
EvalRunKeySchema,
2222
InferenceDatasetSchema,
2323
} from './eval';
24+
import { LogRecordSchema } from './log';
2425
import {
2526
GenerationCommonConfigSchema,
2627
MessageSchema,
@@ -63,6 +64,28 @@ export const ListTracesResponseSchema = z.object({
6364

6465
export type ListTracesResponse = z.infer<typeof ListTracesResponseSchema>;
6566

67+
export const LogQueryFilterSchema = z.object({
68+
traceId: z.string().optional(),
69+
spanId: z.string().optional(),
70+
});
71+
72+
export type LogQueryFilter = z.infer<typeof LogQueryFilterSchema>;
73+
74+
export const ListLogsRequestSchema = z.object({
75+
limit: z.number().optional(),
76+
continuationToken: z.string().optional(),
77+
filter: LogQueryFilterSchema.optional(),
78+
});
79+
80+
export type ListLogsRequest = z.infer<typeof ListLogsRequestSchema>;
81+
82+
export const ListLogsResponseSchema = z.object({
83+
logs: z.array(LogRecordSchema),
84+
continuationToken: z.string().optional(),
85+
});
86+
87+
export type ListLogsResponse = z.infer<typeof ListLogsResponseSchema>;
88+
6689
export const GetTraceRequestSchema = z.object({
6790
traceId: z.string().describe('ID of the trace.'),
6891
});

genkit-tools/common/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export * from './document';
2323
export * from './env';
2424
export * from './eval';
2525
export * from './evaluator';
26+
export * from './log';
2627
export * from './middleware';
2728
export * from './model';
2829
export * from './prompt';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
18+
import * as z from 'zod';
19+
20+
import { InstrumentationLibrarySchema } from './trace';
21+
22+
extendZodWithOpenApi(z);
23+
24+
/**
25+
* Zod schema for log record data.
26+
*/
27+
export const LogRecordSchema = z
28+
.object({
29+
logId: z.string(), // Server-generated if omitted
30+
traceId: z.string().optional(),
31+
spanId: z.string().optional(),
32+
timestamp: z
33+
.number()
34+
.describe('log recorded time in milliseconds since the epoch'),
35+
severityNumber: z.number().optional(),
36+
severityText: z.string().optional(),
37+
body: z.unknown().optional(), // Represents any value: string, number, boolean, array, or object
38+
attributes: z.record(z.string(), z.unknown()).optional(),
39+
instrumentationLibrary: InstrumentationLibrarySchema.optional(),
40+
})
41+
.openapi('LogRecordData');
42+
43+
export type LogRecordData = z.infer<typeof LogRecordSchema>;
44+
45+
/**
46+
* Log query parameters.
47+
*/
48+
export const LogQuerySchema = z.object({
49+
limit: z.coerce.number().optional(),
50+
continuationToken: z.string().optional(),
51+
traceId: z.string().optional(),
52+
spanId: z.string().optional(),
53+
});
54+
55+
export type LogQuery = z.infer<typeof LogQuerySchema>;
56+
57+
export interface LogQueryResponse {
58+
logs: LogRecordData[];
59+
continuationToken?: string;
60+
}
61+
62+
export interface LogStore {
63+
init(): Promise<void>;
64+
save(logs: LogRecordData[]): Promise<void>;
65+
list(query?: LogQuery): Promise<LogQueryResponse>;
66+
}

0 commit comments

Comments
 (0)