feat(observability): add v2 genai tracing
This commit is contained in:
parent
e3a39b214f
commit
0271872c09
42 changed files with 3150 additions and 373 deletions
|
|
@ -11,6 +11,7 @@ import { Effect, Layer, Schema } from "effect"
|
|||
import { HttpRouter, HttpServerRequest } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { requestRef, type LocationServices } from "../location"
|
||||
import { ServerObservability } from "../observability"
|
||||
|
||||
export class FormLocationMiddleware extends HttpApiMiddleware.Service<
|
||||
FormLocationMiddleware,
|
||||
|
|
@ -47,28 +48,27 @@ export const formLocationLayer = Layer.effect(
|
|||
}),
|
||||
),
|
||||
)
|
||||
const row = yield* db
|
||||
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row) {
|
||||
return yield* new SessionNotFoundError({
|
||||
sessionID,
|
||||
message: `Session not found: ${sessionID}`,
|
||||
const location = yield* Effect.gen(function* () {
|
||||
const row = yield* db
|
||||
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row) {
|
||||
return yield* new SessionNotFoundError({
|
||||
sessionID,
|
||||
message: `Session not found: ${sessionID}`,
|
||||
})
|
||||
}
|
||||
return Location.Ref.make({
|
||||
directory: AbsolutePath.make(row.directory),
|
||||
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
|
||||
})
|
||||
}
|
||||
}).pipe(Effect.tapCause((cause) => ServerObservability.locationFailure(cause, sessionID, "resolve")))
|
||||
|
||||
return yield* effect.pipe(
|
||||
Effect.provide(
|
||||
locations.get(
|
||||
Location.Ref.make({
|
||||
directory: AbsolutePath.make(row.directory),
|
||||
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.provide(ServerObservability.locationLayer(locations.get(location), sessionID)),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { Effect, Layer, Schema } from "effect"
|
|||
import { HttpRouter } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors"
|
||||
import { ServerObservability } from "../observability"
|
||||
import type { LocationServices } from "../location"
|
||||
|
||||
export class SessionLocationMiddleware extends HttpApiMiddleware.Service<
|
||||
|
|
@ -39,28 +40,27 @@ export const sessionLocationLayer = Layer.effect(
|
|||
}),
|
||||
),
|
||||
)
|
||||
const row = yield* db
|
||||
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row)
|
||||
return yield* new SessionNotFoundError({
|
||||
sessionID,
|
||||
message: `Session not found: ${sessionID}`,
|
||||
const location = yield* Effect.gen(function* () {
|
||||
const row = yield* db
|
||||
.select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, sessionID))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row)
|
||||
return yield* new SessionNotFoundError({
|
||||
sessionID,
|
||||
message: `Session not found: ${sessionID}`,
|
||||
})
|
||||
return Location.Ref.make({
|
||||
directory: AbsolutePath.make(row.directory),
|
||||
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
|
||||
})
|
||||
|
||||
return yield* effect.pipe(
|
||||
Effect.provide(
|
||||
locations.get(
|
||||
Location.Ref.make({
|
||||
directory: AbsolutePath.make(row.directory),
|
||||
workspaceID: row.workspaceID ? WorkspaceV2.ID.make(row.workspaceID) : undefined,
|
||||
}),
|
||||
),
|
||||
),
|
||||
}).pipe(
|
||||
Effect.tapCause((cause) => ServerObservability.locationFailure(cause, sessionID, "resolve")),
|
||||
)
|
||||
|
||||
return yield* effect.pipe(Effect.provide(ServerObservability.locationLayer(locations.get(location), sessionID)))
|
||||
}),
|
||||
)
|
||||
}),
|
||||
|
|
|
|||
33
packages/server/src/observability.ts
Normal file
33
packages/server/src/observability.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
export * as ServerObservability from "./observability"
|
||||
|
||||
import { Cause, Effect, Layer, Option } from "effect"
|
||||
import { HttpMiddleware } from "effect/unstable/http"
|
||||
|
||||
export const httpTracingDisabled = Layer.succeed(HttpMiddleware.TracerDisabledWhen, () => true)
|
||||
|
||||
export function locationLayer<A, E, R>(layer: Layer.Layer<A, E, R>, sessionID: string) {
|
||||
return layer.pipe(Layer.tapCause((cause) => locationFailure(cause, sessionID, "load")))
|
||||
}
|
||||
|
||||
export function locationFailure(cause: Cause.Cause<unknown>, sessionID: string, phase: "resolve" | "load") {
|
||||
if (Cause.hasInterruptsOnly(cause)) return Effect.void
|
||||
const error = Option.getOrUndefined(Cause.findErrorOption(cause))
|
||||
const log = error && typeof error === "object" && "_tag" in error && error._tag === "SessionNotFoundError"
|
||||
? Effect.logWarning
|
||||
: Effect.logError
|
||||
return log("Failed to resolve Session location", cause).pipe(
|
||||
Effect.annotateLogs({
|
||||
operation: "session.location",
|
||||
phase,
|
||||
sessionID,
|
||||
errorType: errorType(cause),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function errorType(cause: Cause.Cause<unknown>) {
|
||||
const error = Option.getOrUndefined(Cause.findErrorOption(cause))
|
||||
if (error && typeof error === "object" && "_tag" in error && typeof error._tag === "string") return error._tag
|
||||
const failure = Cause.squash(cause)
|
||||
return failure instanceof Error ? failure.name : "unknown"
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import { PtyEnvironment } from "./pty-environment"
|
|||
import { layer } from "./location"
|
||||
import { formLocationLayer } from "./middleware/form-location"
|
||||
import { sessionLocationLayer } from "./middleware/session-location"
|
||||
import { ServerObservability } from "./observability"
|
||||
|
||||
const applicationServices = LayerNode.group([
|
||||
Database.node,
|
||||
|
|
@ -95,6 +96,7 @@ function makeRoutes<AuthError, AuthServices>(
|
|||
Layer.provide(auth),
|
||||
Layer.provide(serviceLayer),
|
||||
Layer.provide(Observability.layer),
|
||||
Layer.merge(ServerObservability.httpTracingDisabled),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue