Guide · May 2026
How to Log OpenAI API Calls in Production
OpenAI's dashboard shows you aggregate token usage and costs. What it doesn't show you is which specific calls failed, what prompts triggered unexpected responses, how latency varies by user, or which features are actually consuming your budget. For that you need your own logging. This guide covers everything you need to know.
OpenAIGPT-4oProduction loggingAPI monitoringToken tracking
Why OpenAI's built-in logging isn't enough
OpenAI provides a usage dashboard that shows total tokens consumed and estimated costs. It's useful for billing but useless for debugging. When a user reports that your AI feature gave them a bad response, the OpenAI dashboard can't tell you what prompt triggered it, what the model returned, how long it took, or whether the problem is reproducible.
What you actually need in production:
Input/output logging
The exact prompt sent and response received for every call, so you can reproduce and debug issues.
Per-request token counts
Not just total usage, but tokens per call, per user, per feature — so you know what's expensive.
Latency tracking
How long each call takes. GPT-4o can vary from 500ms to 30 seconds depending on prompt length and load.
Error logging
Rate limit errors, timeouts, and content policy rejections need to be tracked separately from successful calls.
User attribution
Which of your users or customers triggered each call, so you can debug customer-specific issues and allocate costs.
What to log on every OpenAI API call
At minimum, every log entry should capture these fields:
Log fields
{
agent: "gpt-4o", // model used
action: "email_draft", // what the call was for
status: "success", // success | error | pending
input: userPrompt, // the prompt sent
output: responseText, // the response received
tokens: 312, // total tokens used
latency_ms: 1842, // how long it took
cost_usd: 0.0021, // estimated cost
user: "user@example.com" // who triggered it
}The action field is important — it's the business context for the call. Not just "gpt-4o was called" but "an email was drafted". This lets you filter logs by feature and understand which parts of your product are working.
Basic logging with fetch
The simplest approach — make your OpenAI call, then immediately fire a log. The log call is fire-and-forget so it never blocks your response.
JavaScript
const start = Date.now()
// Your existing OpenAI call — unchanged
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: userPrompt }]
})
const output = response.choices[0].message.content
const latency = Date.now() - start
// Log immediately after — fire and forget
fetch('https://logwick.io/api/v1/logs', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.LOGWICK_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
agent: 'gpt-4o',
action: 'email_draft', // what this call was for
status: 'success',
input: userPrompt,
output: output,
tokens: response.usage.total_tokens,
latency_ms: latency,
user: currentUser.email,
cost_usd: response.usage.total_tokens * 0.000005
})
}).catch(() => {}) // never throws, never blocks
return outputLogging with the OpenAI SDK
The same pattern works with the official OpenAI Node.js SDK. The SDK returns the same response object including usage.total_tokens and usage.prompt_tokens.
JavaScript — OpenAI SDK
import OpenAI from 'openai'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
async function callWithLogging(prompt, action, user) {
const start = Date.now()
let status = 'success'
let output = ''
let tokens = 0
try {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }]
})
output = response.choices[0].message.content
tokens = response.usage.total_tokens
} catch (err) {
status = 'error'
output = err.message
}
// Log regardless of success or failure
fetch('https://logwick.io/api/v1/logs', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.LOGWICK_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
agent: 'gpt-4o',
action,
status,
input: prompt,
output,
tokens,
latency_ms: Date.now() - start,
user
})
}).catch(() => {})
if (status === 'error') throw new Error(output)
return output
}Building a reusable wrapper
If you're making OpenAI calls in multiple places, a wrapper function avoids repeating the logging code everywhere. Here's a clean pattern that works across your entire codebase.
lib/ai.js — reusable wrapper
// lib/ai.js
import OpenAI from 'openai'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
const LOGWICK_KEY = process.env.LOGWICK_API_KEY
export async function chat(messages, { action, user, model = 'gpt-4o' } = {}) {
const start = Date.now()
const prompt = messages.map(m => m.content).join('\n')
try {
const response = await openai.chat.completions.create({ model, messages })
const output = response.choices[0].message.content
log({ action, user, model, status: 'success',
input: prompt, output,
tokens: response.usage.total_tokens,
latency_ms: Date.now() - start })
return output
} catch (err) {
log({ action, user, model, status: 'error',
input: prompt, output: err.message,
latency_ms: Date.now() - start })
throw err
}
}
function log(data) {
if (!LOGWICK_KEY) return
fetch('https://logwick.io/api/v1/logs', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + LOGWICK_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ agent: data.model, ...data })
}).catch(() => {})
}
// Usage anywhere in your app:
// import { chat } from './lib/ai'
// const reply = await chat(messages, { action: 'email_draft', user: req.user.email })Using the Logwick SDK (fastest approach)
The Logwick SDK includes a built-in OpenAI wrapper that handles all the timing, error handling, and token counting automatically.
Install
npm install logwick
JavaScript
import { LogwickClient } from 'logwick'
const logwick = new LogwickClient({ apiKey: process.env.LOGWICK_API_KEY })
// Wrap your existing OpenAI call — nothing else changes
const result = await logwick.openai(
() => openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }]
}),
{ action: 'email_draft', user: req.user.email }
)
// result is the normal OpenAI response object
const reply = result.choices[0].message.contentThe wrapper automatically captures timing, token usage, the input prompt, the output, and logs errors. You get a full audit trail for every call with one line of change.
Tracking costs per user and feature
OpenAI's dashboard shows total spend. It doesn't tell you which feature or customer is driving it. By logging the user and action fields alongside cost_usd, you can answer questions like: which customer costs the most to serve, and which feature is most expensive.
GPT-4o cost calculation
// GPT-4o pricing (as of 2026):
// Input: $2.50 per million tokens
// Output: $10.00 per million tokens
function calculateCost(usage) {
const inputCost = (usage.prompt_tokens / 1_000_000) * 2.50
const outputCost = (usage.completion_tokens / 1_000_000) * 10.00
return inputCost + outputCost
}
// Then log it:
logwick.fire({
agent: 'gpt-4o',
action: 'email_draft',
tokens: response.usage.total_tokens,
cost_usd: calculateCost(response.usage),
user: currentUser.email
})Logging errors and timeouts
OpenAI errors fall into three categories: rate limit errors (429), timeout errors, and content policy rejections. Each needs different handling. Log all of them with status: 'error' so you can track error rates over time.
JavaScript — error handling
try {
const response = await openai.chat.completions.create({ ... })
logwick.fire({ status: 'success', ...data })
} catch (err) {
// Log the error with full context
logwick.fire({
agent: 'gpt-4o',
action: action,
status: 'error',
input: prompt,
output: err.message,
latency_ms: Date.now() - start,
user: user,
metadata: {
error_type: err.constructor.name,
error_code: err.status,
// Rate limit: 429, Timeout: ETIMEDOUT, Policy: 400
}
})
throw err
}Logging LangChain + OpenAI calls
If you're using LangChain, the cleanest approach is a callback handler that automatically logs every LLM call in your chain without modifying each call individually.
JavaScript — LangChain callback
import { LogwickCallbackHandler } from 'logwick'
import { LLMChain } from 'langchain/chains'
const handler = new LogwickCallbackHandler(logwick, {
user: req.user.email
})
// Every LLM call in this chain is logged automatically
const chain = new LLMChain({
llm,
prompt,
callbacks: [handler]
})
const result = await chain.call({ input: userQuery })
// Logwick has already logged the call — no extra code neededViewing and searching your logs
Once you're logging, the Logwick dashboard gives you a searchable, filterable view of all your OpenAI calls. You can filter by status to find all errors, search by user to debug a customer issue, or filter by action to understand the performance of a specific feature.
You can also query logs via API or stream them in real time:
Query your logs via API
# Get all errors from the last 24 hours
curl "https://logwick.io/api/v1/logs?status=error&from=2026-05-01" \
-H "Authorization: Bearer sk-lw-your-key"
# Stream logs in real time
curl -N "https://logwick.io/api/v1/logs/stream?status=error" \
-H "Authorization: Bearer sk-lw-your-key"
# Get stats
curl "https://logwick.io/api/v1/stats?days=7" \
-H "Authorization: Bearer sk-lw-your-key"
If you use Claude Desktop, you can also connect the Logwick MCP server and ask questions in plain English: "Show me all failed email_draft calls from yesterday" or "How much did we spend on GPT-4o this week?"
Start logging your OpenAI calls today
Free tier includes 5,000 logs/month. No credit card required. Up and running in 3 minutes.