Riki
Riki8mo ago

Args in Log Streams

Hello, I am currently using Axiom to monitor my convex backend as per showcased in the documentation. When checking and debugging the errors reported by convex to Axiom, I usually want to know what happened in my function: what args and userIdentity where passed to the failing function in order to understand and debug it. But I can not find them reported in Axiom. Would it be possible that convex send those args to the Log Stream pipeline automatically? Not sure what entry should be used, but in any cases, that would be very useful. Otherwise, I'll probably need to write a console.log every time I create a function or modify args which is kinda error-prone. I am kinda new to monitoring/observability, so this may be a dumb question, or there may be already a solution. Just asking in case of. Have a nice day and thanks for your work the convex team!
10 Replies
sshader
sshader8mo ago
Yeah they're not currently sent via log streams, but seems totally reasonable to want them there. To attempt to unblock you right now, have you seen customFunctions (https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md#custom-functions) ? One strategy could be to add a custom wrapper that just does a console.log with the args + userIdentity (potentially only doing this for the subset of fields that are valuable to you + you feel fine showing up in logs). It hopefully helps a bit with needing to constantly add the console.logs
GitHub
convex-helpers/packages/convex-helpers/README.md at main · get-conv...
A collection of useful code to complement the official packages. - get-convex/convex-helpers
Riki
RikiOP8mo ago
Oh pretty interesting workaround @sshader . I didn't event know there were out of the box customFunctions utils in convex-helpers. I will have a look. Thanks! After digging into customFunctions, it looks like I can indeed consume the context and so, the userIdentity. On the other hand, I can not consume args passed to the final query/mutation. The args exposed by customFunctions are just "middleware" args, not the ones of the inner function. But anyway, given this is just an helper, I can write my own version of customFunctions that passes the inner arguments I guess. May be not that easy to type this, but let's see.
Michal Srb
Michal Srb8mo ago
@Riki maybe something like this could work, but I haven't tested it:
const myMutation = customMutation(mutation, {
args: {},
input: async (ctx, args) => {
console.log("args", args);
return { ctx: {}, args };
},
});
const myMutation = customMutation(mutation, {
args: {},
input: async (ctx, args) => {
console.log("args", args);
return { ctx: {}, args };
},
});
Definitely something the library should support.
Riki
RikiOP8mo ago
Unfortunately, these customMutation/customQuery in convex-helpers don't have access to the final args passed to the query/mutation
No description
Michal Srb
Michal Srb8mo ago
What about
const myMutation = customMutation(mutation, {
input: async (ctx, args) => {
console.log("args", args);
return { ctx: {}, args };
},
});
const myMutation = customMutation(mutation, {
input: async (ctx, args) => {
console.log("args", args);
return { ctx: {}, args };
},
});
(Looking at the helpers source, not providing the args key to customMutation should not split the args)
Riki
RikiOP8mo ago
Thanks for the help Michal
Riki
RikiOP8mo ago
Unfortunately, same result
No description
Michal Srb
Michal Srb8mo ago
You are right, I read the code wrong. You'll have to create a custom constructor without helpers.
sshader
sshader8mo ago
Something I also haven't tested but might work:
import { QueryBuilder } from 'convex/server'
import { DataModel } from '../_generated/dataModel'
import { query } from '../_generated/server'

const wrappedQuery: QueryBuilder<DataModel, 'public'> = (fn: any) => {
// handle calls like `wrappedQuery({ args: { ... }, handler: (ctx, args) => { ... } })`
if ('handler' in fn) {
return query({
...fn,
handler: (ctx, args) => {
console.log('args', args)
return fn.handler(ctx, args)
},
})
} else {
// handle calls like `wrappedQuery((ctx, args) => { ... })`
return query((ctx, ...args) => {
console.log('args', args)
return fn.handler(ctx, args)
})
}
}
import { QueryBuilder } from 'convex/server'
import { DataModel } from '../_generated/dataModel'
import { query } from '../_generated/server'

const wrappedQuery: QueryBuilder<DataModel, 'public'> = (fn: any) => {
// handle calls like `wrappedQuery({ args: { ... }, handler: (ctx, args) => { ... } })`
if ('handler' in fn) {
return query({
...fn,
handler: (ctx, args) => {
console.log('args', args)
return fn.handler(ctx, args)
},
})
} else {
// handle calls like `wrappedQuery((ctx, args) => { ... })`
return query((ctx, ...args) => {
console.log('args', args)
return fn.handler(ctx, args)
})
}
}
(the types you want are QueryBuilder, and personally I'd use some anys in the implementation to avoid dealing with some of the more complicated types)
ampp
ampp8mo ago
export const mutation = customMutation(baseMutation, {
args: { sessionId: v.optional(v.union(v.string(), v.null())) },
input: async (baseCtx, { sessionId }) => (
console.log("sessionId", sessionId),
{
ctx: await mutationCtx(baseCtx, sessionId),
args: { sessionId },
}
),
});
export const mutation = customMutation(baseMutation, {
args: { sessionId: v.optional(v.union(v.string(), v.null())) },
input: async (baseCtx, { sessionId }) => (
console.log("sessionId", sessionId),
{
ctx: await mutationCtx(baseCtx, sessionId),
args: { sessionId },
}
),
});
works for me but my mutationCtx is custom.

Did you find this page helpful?