-
-
Notifications
You must be signed in to change notification settings - Fork 336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
jdbi3 SQLLog equivalent? #981
Comments
I think we have removed this logging abstraction in JDBI3. If you want to enable SQL logging, you need to that in the configuration of your SLF4J implementation. |
in Problem is this doesn't leave me free to decide how I want my queries to be logged: level, phrasing, raw/rendered/parsed sql, conditions... The doc site says TimingCollector isn't entirely fleshed out yet anyway so ideas are welcome, so I guess I hereby suggest to either make a sister interface for logging queries rather than passed time, or to generify TimingCollector to also be intended for logging queries. |
Yes, we do not have a great interface for this right now. The old |
Having spent some hours updating my project to jdbi3 now, I'm finding TimingCollector a not-half-bad solution in my project, with just 3 minor gripes: it's semantically wrong, it's executed after the query instead of before (so if the query causes an exception there will be no logging of it), and I'm not able to get the named/positional arguments from the statementcontext's binding. -- Completely unrelated to this issue but on the topic of having spent time updating to jdbi3, I just wanna say I freaking love it. The way the Jdbi class acts as a configuration repository that holds plugins is brilliant. My project was until now built on using jdbi in a very specific, typically-me way that worked but wasn't really jdbi working the jdbi way. Since jdbi3 forced me to do everything the jdbi3 way by removing some freedoms, I've learned some of the alternative mechanisms like Arguments, building on column rather than row mapping, and the improved fluent api, and it's just a whole new world of convenience once you've got everything set up. I didn't even realize how much sense your paradigm technically and functionally makes until now. Awesome work guys, thanks for making this :) |
Thanks for the kind feedback! Would you mind maybe assembling the ways you want to use SqlLog / TimingCollector into this ticket as the start of a design document? It'd be good to get some actual use cases outlined, so we can make sure we solve actual problems. |
Quick pseudo-java-code sketch: public interface Jdbi {
void registerSqlLogger(SqlLogger logger);
void registerSqlLoggerFactory(SqlLoggerFactory factory);
}
public interface SqlLogger {
void logBeforeExecution(StatementContext context);
void logAfterExecution(StatementContext context, long nanos);
void logException(StatementContext context, Exception ex);
}
public interface SqlLoggerFactory {
// the sqlObject, if any
Optional<SqlLogger> build(@Nullable SqlObject extension, @Nullable Class extensionClass);
}
public interface StatementContext {
// "select * from x where foo = 'bar'"
String getNormalizedSql();
// "select * from x where foo = ?"
// "select * from x where foo = :bar"
String getParamaterizedSql();
List<Object> getPositionalArgs();
Map<String, Object> getNamedArgs();
ParamsType getParamsType();
enum ParamsType {
NONE, POSITIONAL, NAMED
}
} public class Main {
public static void main(String[] args) {
Jdbi jdbi = Jdbi.create();
jdbi.registerSqlLoggerFactory((sqlObject, sqlObjectClass) -> new SqlLogger() {
private final Logger logger = LoggerFactory.getLogger(sqlObject != null? sqlObjectClass: MyApp.class);
@Override
void before(StatementContext context) {
String params;
switch (context.getParamsType()) {
case NONE:
params = "N/A";
case POSITIONAL:
params = context.getPositionalArgs().toString();
case NAMED:
params = toKeyValueString(context.getNamedArgs());
}
logger.debug(Markers.SQL, "query: {}, params: {}", context.getParameterizedSql(), params);
}
@Override
void after(StatementContext context, long nanos) {
logger.trace(Markers.SQL, "query {} took {}ns", context.getNormalizedSql, nanos);
}
@Override
void exception(StatementContext context, Exception ex) {
logger.error(Markers.SQL, context.getNormalizedSql(), e);
}
});
}
} |
For what it's worth, a simple SqlStatementCustomizerFactory allows you to implement this through annotations, placed either on individual methods or entire interfaces. Yes, it's prone to being forgotten, which is an issue. I currently do not need extremely precise logging for queries as they are mostly for debugging, and turned on when I need to, but you can override beforeExecution and afterExecution for the timing too. It isn't too hard to extend it to take a class to instantiate instead. @NameBinding
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@SqlStatementCustomizingAnnotation(WriteQueryToLogFactory::class)
annotation class WriteQueryToLog
class WriteQueryToLogFactory : SqlStatementCustomizerFactory {
val log: Logger = LoggerFactory.getLogger("api.sql-statements")
override fun createForType(annotation: Annotation?, sqlObjectType: Class<*>?): SqlStatementCustomizer {
return SqlStatementCustomizer( { q -> q.addCustomizer(object: StatementCustomizer {
override fun beforeExecution(stmt: PreparedStatement, ctx: StatementContext) {
log.trace("Context: ${ctx.extensionMethod.type}.${ctx.extensionMethod.method.name}")
log.trace("Statement: ${stmt.toString()}")
}
})})
}
} Yes, it means you have access to the entire statement and can screw it up, but is it really an issue? |
@pikzen That gets the brunt of it done, but my suggestion was about making the query data more accessible and adding the onException method as well. I tried one of those customizing annotations at first too but found it too limited for what I wanted to do, which is why I suggested all of this. |
I just had another look and it seems ParsedParameters and ParsedSql are halfway to what I'd like to see. ParsedParameters is just missing a way to get a parameter value from the provided name/index. I think that would make the picture largely complete. |
Have you given a look at the StatementContext object passed to the customizer? It gives you access to I do agree that getting something on an exception would be practical, but isn't that better served by simply improving the current StatementCustomizer interface and adapting core? |
Indeed, my bad, I meant There's a few places where they are available yes, binding.positionals/named, statement.parameterValues/parameterTypes (although that one is not exactly the most practical) and they are indeed only available through reflection. I |
Bound parameters are in |
I've been going through the entire javadoc and site doc but I can't seem to find a replacement mechanism for SQLLog. I can see StatementContext has some get*Sql methods, but there's no replacement for DBI.setSQLLog. TimingCollector receives the statementcontext so it could technically be done in there, but that feels like a workaround given the interface's name.
The text was updated successfully, but these errors were encountered: