1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2001-2004 Novell, Inc.
6 * Copyright (C) 2008 Red Hat, Inc.
16 #include "soup-logger.h"
17 #include "soup-message.h"
22 * @short_description: Debug logging support
24 * #SoupLogger watches a #SoupSession and logs the HTTP traffic that
25 * it generates, for debugging purposes. Many applications use an
26 * environment variable to determine whether or not to use
27 * #SoupLogger, and to determine the amount of debugging output.
29 * To use #SoupLogger, first create a logger with soup_logger_new(),
30 * optionally configure it with soup_logger_set_request_filter(),
31 * soup_logger_set_response_filter(), and soup_logger_set_printer(),
32 * and then attach it to a session (or multiple sessions) with
33 * soup_logger_attach().
35 * By default, the debugging output is sent to %stdout, and looks
38 * <informalexample><screen>
39 * > POST /unauth HTTP/1.1
40 * > Soup-Debug-Timestamp: 1200171744
41 * > Soup-Debug: SoupSessionAsync 1 (0x612190), SoupMessage 1 (0x617000), SoupSocket 1 (0x612220)
43 * > Content-Type: text/plain
48 * < HTTP/1.1 201 Created
49 * < Soup-Debug-Timestamp: 1200171744
50 * < Soup-Debug: SoupMessage 1 (0x617000)
51 * < Date: Sun, 12 Jan 2008 21:02:24 GMT
52 * < Content-Length: 0
53 * </screen></informalexample>
55 * The <literal>Soup-Debug-Timestamp</literal> line gives the time (as
56 * a #time_t) when the request was sent, or the response fully
59 * The <literal>Soup-Debug</literal> line gives further debugging
60 * information about the #SoupSession, #SoupMessage, and #SoupSocket
61 * involved; the hex numbers are the addresses of the objects in
62 * question (which may be useful if you are running in a debugger).
63 * The decimal IDs are simply counters that uniquely identify objects
64 * across the lifetime of the #SoupLogger. In particular, this can be
65 * used to identify when multiple messages are sent across the same
69 G_DEFINE_TYPE (SoupLogger, soup_logger, G_TYPE_OBJECT)
72 /* We use a mutex so that if requests are being run in
73 * multiple threads, we don't mix up the output.
80 SoupLoggerLogLevel level;
83 SoupLoggerFilter request_filter;
84 gpointer request_filter_data;
85 GDestroyNotify request_filter_dnotify;
87 SoupLoggerFilter response_filter;
88 gpointer response_filter_data;
89 GDestroyNotify response_filter_dnotify;
91 SoupLoggerPrinter printer;
92 gpointer printer_data;
93 GDestroyNotify printer_dnotify;
95 #define SOUP_LOGGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_LOGGER, SoupLoggerPrivate))
98 soup_logger_init (SoupLogger *logger)
100 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
102 priv->lock = g_mutex_new ();
103 priv->tag = g_quark_from_static_string (g_strdup_printf ("SoupLogger-%p", logger));
104 priv->ids = g_hash_table_new (NULL, NULL);
108 finalize (GObject *object)
110 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (object);
112 g_hash_table_destroy (priv->ids);
114 if (priv->request_filter_dnotify)
115 priv->request_filter_dnotify (priv->request_filter_data);
116 if (priv->response_filter_dnotify)
117 priv->response_filter_dnotify (priv->response_filter_data);
118 if (priv->printer_dnotify)
119 priv->printer_dnotify (priv->printer_data);
121 g_mutex_free (priv->lock);
123 G_OBJECT_CLASS (soup_logger_parent_class)->finalize (object);
127 soup_logger_class_init (SoupLoggerClass *logger_class)
129 GObjectClass *object_class = G_OBJECT_CLASS (logger_class);
131 g_type_class_add_private (logger_class, sizeof (SoupLoggerPrivate));
133 object_class->finalize = finalize;
137 * SoupLoggerLogLevel:
138 * @SOUP_LOGGER_LOG_NONE: No logging
139 * @SOUP_LOGGER_LOG_MINIMAL: Log the Request-Line or Status-Line and
140 * the Soup-Debug pseudo-headers
141 * @SOUP_LOGGER_LOG_HEADERS: Log the full request/response headers
142 * @SOUP_LOGGER_LOG_BODY: Log the full headers and request/response
145 * Describes the level of logging output to provide.
150 * @level: the debug level
151 * @max_body_size: the maximum body size to output, or -1
153 * Creates a new #SoupLogger with the given debug level. If @level is
154 * %SOUP_LOGGER_LOG_BODY, @max_body_size gives the maximum number of
155 * bytes of the body that will be logged. (-1 means "no limit".)
157 * If you need finer control over what message parts are and aren't
158 * logged, use soup_logger_set_request_filter() and
159 * soup_logger_set_response_filter().
161 * Returns: a new #SoupLogger
164 soup_logger_new (SoupLoggerLogLevel level, int max_body_size)
167 SoupLoggerPrivate *priv;
169 logger = g_object_new (SOUP_TYPE_LOGGER, NULL);
171 priv = SOUP_LOGGER_GET_PRIVATE (logger);
173 priv->max_body_size = max_body_size;
180 * @logger: the #SoupLogger
181 * @msg: the message being logged
182 * @user_data: the data passed to soup_logger_set_request_filter()
183 * or soup_logger_set_response_filter()
185 * The prototype for a logging filter. The filter callback will be
186 * invoked for each request or response, and should analyze it and
187 * return a #SoupLoggerLogLevel value indicating how much of the
188 * message to log. Eg, it might choose between %SOUP_LOGGER_LOG_BODY
189 * and %SOUP_LOGGER_LOG_HEADERS depending on the Content-Type.
191 * Return value: a #SoupLoggerLogLevel value indicating how much of
196 * soup_logger_set_request_filter:
197 * @logger: a #SoupLogger
198 * @request_filter: the callback for request debugging
199 * @filter_data: data to pass to the callback
200 * @destroy: a #GDestroyNotify to free @filter_data
202 * Sets up a filter to determine the log level for a given request.
203 * For each HTTP request @logger will invoke @request_filter to
204 * determine how much (if any) of that request to log. (If you do not
205 * set a request filter, @logger will just always log requests at the
206 * level passed to soup_logger_new().)
209 soup_logger_set_request_filter (SoupLogger *logger,
210 SoupLoggerFilter request_filter,
211 gpointer filter_data,
212 GDestroyNotify destroy)
214 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
216 priv->request_filter = request_filter;
217 priv->request_filter_data = filter_data;
218 priv->request_filter_dnotify = destroy;
222 * soup_logger_set_response_filter:
223 * @logger: a #SoupLogger
224 * @response_filter: the callback for response debugging
225 * @filter_data: data to pass to the callback
226 * @destroy: a #GDestroyNotify to free @filter_data
228 * Sets up a filter to determine the log level for a given response.
229 * For each HTTP response @logger will invoke @response_filter to
230 * determine how much (if any) of that response to log. (If you do not
231 * set a response filter, @logger will just always log responses at
232 * the level passed to soup_logger_new().)
235 soup_logger_set_response_filter (SoupLogger *logger,
236 SoupLoggerFilter response_filter,
237 gpointer filter_data,
238 GDestroyNotify destroy)
240 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
242 priv->response_filter = response_filter;
243 priv->response_filter_data = filter_data;
244 priv->response_filter_dnotify = destroy;
249 * @logger: the #SoupLogger
250 * @level: the level of the information being printed.
251 * @direction: a single-character prefix to @data
252 * @data: data to print
253 * @user_data: the data passed to soup_logger_set_printer()
255 * The prototype for a custom printing callback.
257 * @level indicates what kind of information is being printed. Eg, it
258 * will be %SOUP_LOGGER_LOG_HEADERS if @data is header data.
260 * @direction is either '<', '>', or ' ', and @data is the single line
261 * to print; the printer is expected to add a terminating newline.
263 * To get the effect of the default printer, you would do:
265 * <informalexample><programlisting>
266 * printf ("%c %s\n", direction, data);
267 * </programlisting></informalexample>
271 * soup_logger_set_printer:
272 * @logger: a #SoupLogger
273 * @printer: the callback for printing logging output
274 * @printer_data: data to pass to the callback
275 * @destroy: a #GDestroyNotify to free @printer_data
277 * Sets up an alternate log printing routine, if you don't want
278 * the log to go to %stdout.
281 soup_logger_set_printer (SoupLogger *logger,
282 SoupLoggerPrinter printer,
283 gpointer printer_data,
284 GDestroyNotify destroy)
286 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
288 priv->printer = printer;
289 priv->printer_data = printer_data;
290 priv->printer_dnotify = destroy;
294 soup_logger_get_id (SoupLogger *logger, gpointer object)
296 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
298 return GPOINTER_TO_UINT (g_object_get_qdata (object, priv->tag));
302 soup_logger_set_id (SoupLogger *logger, gpointer object)
304 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
305 gpointer klass = G_OBJECT_GET_CLASS (object);
308 id = g_hash_table_lookup (priv->ids, klass);
310 g_hash_table_insert (priv->ids, klass, id);
312 g_object_set_qdata (object, priv->tag, id);
313 return GPOINTER_TO_UINT (id);
317 soup_logger_clear_id (SoupLogger *logger, gpointer object)
319 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
321 g_object_set_qdata (object, priv->tag, NULL);
324 static void request_queued (SoupSession *session, SoupMessage *msg,
326 static void request_started (SoupSession *session, SoupMessage *msg,
327 SoupSocket *socket, gpointer user_data);
328 static void request_unqueued (SoupSession *session, SoupMessage *msg,
332 weak_notify_unref (gpointer logger, GObject *ex_session)
334 g_object_unref (logger);
338 * soup_logger_attach:
339 * @logger: a #SoupLogger
340 * @session: a #SoupSession
342 * Sets @logger to watch @session and print debug information for
345 * (The session will take a reference on @logger, which will be
346 * removed when you call soup_logger_detach(), or when the session is
350 soup_logger_attach (SoupLogger *logger,
351 SoupSession *session)
353 if (!soup_logger_get_id (logger, session))
354 soup_logger_set_id (logger, session);
355 g_signal_connect (session, "request_queued",
356 G_CALLBACK (request_queued), logger);
357 g_signal_connect (session, "request_started",
358 G_CALLBACK (request_started), logger);
359 g_signal_connect (session, "request_unqueued",
360 G_CALLBACK (request_unqueued), logger);
362 g_object_weak_ref (G_OBJECT (session),
363 weak_notify_unref, g_object_ref (logger));
367 * soup_logger_detach:
368 * @logger: a #SoupLogger
369 * @session: a #SoupSession
371 * Stops @logger from watching @session.
374 soup_logger_detach (SoupLogger *logger,
375 SoupSession *session)
377 g_signal_handlers_disconnect_by_func (session, request_queued, logger);
378 g_signal_handlers_disconnect_by_func (session, request_started, logger);
379 g_signal_handlers_disconnect_by_func (session, request_unqueued, logger);
381 g_object_weak_unref (G_OBJECT (session),
382 weak_notify_unref, logger);
386 soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level,
387 char direction, const char *format, ...)
389 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
391 char *data, *line, *end;
393 va_start (args, format);
394 data = g_strdup_vprintf (format, args);
397 if (level == SOUP_LOGGER_LOG_BODY && priv->max_body_size > 0) {
398 if (strlen (data) > priv->max_body_size + 6)
399 strcpy (data + priv->max_body_size, "\n[...]");
404 end = strchr (line, '\n');
408 priv->printer (logger, level, direction,
409 line, priv->printer_data);
411 printf ("%c %s\n", direction, line);
414 } while (end && *line);
420 soup_logger_print_basic_auth (SoupLogger *logger, const char *value)
425 decoded = (char *)g_base64_decode (value + 6, &len);
427 decoded = g_strdup (value);
428 p = strchr (decoded, ':');
430 while (++p < decoded + len)
433 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
434 "Authorization: Basic [%.*s]", len, decoded);
439 print_request (SoupLogger *logger, SoupMessage *msg,
440 SoupSession *session, SoupSocket *socket,
443 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
444 SoupLoggerLogLevel log_level;
445 SoupMessageHeadersIter iter;
446 const char *name, *value;
449 if (priv->request_filter) {
450 log_level = priv->request_filter (logger, msg,
451 priv->request_filter_data);
453 log_level = priv->level;
455 if (log_level == SOUP_LOGGER_LOG_NONE)
458 uri = soup_message_get_uri (msg);
459 if (msg->method == SOUP_METHOD_CONNECT) {
460 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
461 "CONNECT %s:%u HTTP/1.%d",
462 uri->host, uri->port,
463 soup_message_get_http_version (msg));
465 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
466 "%s %s%s%s HTTP/1.%d",
467 msg->method, uri->path,
468 uri->query ? "?" : "",
469 uri->query ? uri->query : "",
470 soup_message_get_http_version (msg));
473 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
474 "Soup-Debug-Timestamp: %lu",
475 (unsigned long)time (0));
476 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
477 "Soup-Debug: %s %u (%p), %s %u (%p), %s %u (%p)%s",
478 g_type_name_from_instance ((GTypeInstance *)session),
479 soup_logger_get_id (logger, session), session,
480 g_type_name_from_instance ((GTypeInstance *)msg),
481 soup_logger_get_id (logger, msg), msg,
482 g_type_name_from_instance ((GTypeInstance *)socket),
483 soup_logger_get_id (logger, socket), socket,
484 restarted ? ", restarted" : "");
486 if (log_level == SOUP_LOGGER_LOG_MINIMAL)
489 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
490 "Host: %s", uri->host);
491 soup_message_headers_iter_init (&iter, msg->request_headers);
492 while (soup_message_headers_iter_next (&iter, &name, &value)) {
493 if (!g_ascii_strcasecmp (name, "Authorization") &&
494 !g_ascii_strncasecmp (value, "Basic ", 6))
495 soup_logger_print_basic_auth (logger, value);
497 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
498 "%s: %s", name, value);
501 if (log_level == SOUP_LOGGER_LOG_HEADERS)
504 if (msg->request_body->length) {
507 request = soup_message_body_flatten (msg->request_body);
508 soup_buffer_free (request);
510 if (soup_message_headers_get_expectations (msg->request_headers) != SOUP_EXPECTATION_CONTINUE) {
511 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>',
512 "\n%s", msg->request_body->data);
518 print_response (SoupLogger *logger, SoupMessage *msg)
520 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
521 SoupLoggerLogLevel log_level;
522 SoupMessageHeadersIter iter;
523 const char *name, *value;
525 if (priv->response_filter) {
526 log_level = priv->response_filter (logger, msg,
527 priv->response_filter_data);
529 log_level = priv->level;
531 if (log_level == SOUP_LOGGER_LOG_NONE)
534 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
536 soup_message_get_http_version (msg),
537 msg->status_code, msg->reason_phrase);
539 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
540 "Soup-Debug-Timestamp: %lu",
541 (unsigned long)time (0));
542 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
543 "Soup-Debug: %s %u (%p)",
544 g_type_name_from_instance ((GTypeInstance *)msg),
545 soup_logger_get_id (logger, msg), msg);
547 if (log_level == SOUP_LOGGER_LOG_MINIMAL)
550 soup_message_headers_iter_init (&iter, msg->response_headers);
551 while (soup_message_headers_iter_next (&iter, &name, &value)) {
552 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '<',
553 "%s: %s", name, value);
555 if (log_level == SOUP_LOGGER_LOG_HEADERS)
558 if (msg->response_body->length) {
559 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '<',
560 "\n%s", msg->response_body->data);
565 got_informational (SoupMessage *msg, gpointer user_data)
567 SoupLogger *logger = user_data;
568 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
570 g_mutex_lock (priv->lock);
572 print_response (logger, msg);
573 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
575 if (msg->status_code == SOUP_STATUS_CONTINUE && msg->request_body->data) {
576 SoupLoggerLogLevel log_level;
578 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
579 "[Now sending request body...]");
581 if (priv->request_filter) {
582 log_level = priv->request_filter (logger, msg,
583 priv->request_filter_data);
585 log_level = priv->level;
587 if (log_level == SOUP_LOGGER_LOG_BODY) {
588 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>',
589 "%s", msg->request_body->data);
592 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
595 g_mutex_unlock (priv->lock);
599 got_body (SoupMessage *msg, gpointer user_data)
601 SoupLogger *logger = user_data;
602 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
604 g_mutex_lock (priv->lock);
606 print_response (logger, msg);
607 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
609 g_mutex_unlock (priv->lock);
613 request_queued (SoupSession *session, SoupMessage *msg, gpointer user_data)
615 SoupLogger *logger = user_data;
617 g_signal_connect (msg, "got-informational",
618 G_CALLBACK (got_informational),
620 g_signal_connect (msg, "got-body",
621 G_CALLBACK (got_body),
626 request_started (SoupSession *session, SoupMessage *msg,
627 SoupSocket *socket, gpointer user_data)
629 SoupLogger *logger = user_data;
633 msg_id = soup_logger_get_id (logger, msg);
637 soup_logger_set_id (logger, msg);
641 if (!soup_logger_get_id (logger, socket))
642 soup_logger_set_id (logger, socket);
644 print_request (logger, msg, session, socket, restarted);
645 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
649 request_unqueued (SoupSession *session, SoupMessage *msg, gpointer user_data)
651 SoupLogger *logger = user_data;
653 g_signal_handlers_disconnect_by_func (msg, got_informational, logger);
654 g_signal_handlers_disconnect_by_func (msg, got_body, logger);
656 soup_logger_clear_id (logger, msg);