In the previous chapter, Session Tracing & Context Propagation, we learned how to record the "video" of our applicationβtracking how long operations take and how they nest inside each other.
However, sometimes you don't need a video. Sometimes, you just need a photograph.
Welcome to Discrete Event Logging.
If Spans are a continuous video recording of a birthday party (showing the flow of time), Events are the Polaroid photos you snap at specific moments.
Unlike a Span, an Event doesn't have a "duration." It just happens. We use events to track things like "A tool was loaded," "An error popped up," or " The user rejected a permission."
Imagine Claude Code is starting up. It scans your folder and finds available "Skills" (tools it can use, like Git or a File Reader).
We want to answer: "Which skills were available to the user in this session?"
We don't care how long it took to find them (that's a Span). We just want a list.
Events happen instantly. In our telemetry system, we record the exact millisecond the event occurred.
Just like every photo needs a date, every event needs standard context:
This is the custom writing on the back of the photo. For our use case, the payload contains the skill_name and skill_source.
Let's look at how we implement the "Tool Inventory" check using skillLoadedEvent.ts.
We iterate through the list of skills and snap a photo (log an event) for each one.
First, we import the generic logEvent function.
// From skillLoadedEvent.ts
import { logEvent } from '../../services/analytics/index.js'
import { getSkillToolCommands } from '../../commands.js'
We loop through the skills and fire an event for each one.
export async function logSkillsLoaded(cwd: string): Promise<void> {
const skills = await getSkillToolCommands(cwd)
for (const skill of skills) {
// Only log prompts, skip other types for now
if (skill.type !== 'prompt') continue
// SNAP THE PHOTO!
logEvent('tengu_skill_loaded', {
_PROTO_skill_name: skill.name,
skill_source: skill.source,
skill_loaded_from: skill.loadedFrom
})
}
}
Note on Safety: You might see complex types in the real code (like
AnalyticsMetadata_...). These are safety tags that tell the system, "I promise I checked this data, and it doesn't contain secret passwords." We'll cover privacy in the next chapter.
What happens inside logEvent? It's not just a console.log. It needs to attach metadata and send it to the OpenTelemetry system.
Let's look under the hood at events.ts.
Computers are fast. Sometimes two events happen in the same millisecond. To ensure we know the exact order, we use a simple counter variable.
// From events.ts
// A global counter that goes up by 1 every time we log something
let eventSequence = 0
When logOTelEvent is called, we mix the user's data with system data.
export async function logOTelEvent(eventName: string, metadata: {}): Promise<void> {
const eventLogger = getEventLogger()
// 1. Combine global info (OS, Version) with event info
const attributes: Attributes = {
...getTelemetryAttributes(), // e.g. { os: "macOS" }
'event.name': eventName, // e.g. "tengu_skill_loaded"
'event.timestamp': new Date().toISOString(),
'event.sequence': eventSequence++, // Increment the counter!
}
Why
eventSequence++? This ensures that even if two events have the exact same timestamp, we can sort them bysequencenumber later to reconstruct the timeline perfectly.
We take the custom data (like skill_name), add it to the attributes, and finally "emit" (send) the record.
// 2. Add the custom data passed by the user
for (const [key, value] of Object.entries(metadata)) {
if (value !== undefined) {
attributes[key] = value
}
}
// 3. Send the "Photo" to the backend
eventLogger.emit({
body: `claude_code.${eventName}`,
attributes,
})
}
The system is robust. If the logger isn't ready (maybe the app is crashing), it fails gracefully.
if (!eventLogger) {
// If the camera is broken, just print a warning and stop.
// We don't want to crash the app just because logging failed.
logForDebugging(`Event dropped: ${eventName}`, { level: 'warn' })
return
}
In this chapter, we learned:
eventSequence) to keep photos in order.We briefly touched on "Safety Tags" in the code snippets. Since we are collecting data about user behavior, we must be extremely careful not to accidentally record passwords, API keys, or personal files.
In the next chapter, we will learn how to secure this data using hashing and privacy metadata.
Next Chapter: Privacy-Aware Metadata & Hashing
Generated by Code IQ