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"
18 #include "soup-session.h"
19 #include "soup-session-feature.h"
20 #include "soup-socket.h"
25 * @short_description: Debug logging support
27 * #SoupLogger watches a #SoupSession and logs the HTTP traffic that
28 * it generates, for debugging purposes. Many applications use an
29 * environment variable to determine whether or not to use
30 * #SoupLogger, and to determine the amount of debugging output.
32 * To use #SoupLogger, first create a logger with soup_logger_new(),
33 * optionally configure it with soup_logger_set_request_filter(),
34 * soup_logger_set_response_filter(), and soup_logger_set_printer(),
35 * and then attach it to a session (or multiple sessions) with
36 * soup_session_add_feature().
38 * By default, the debugging output is sent to %stdout, and looks
41 * <informalexample><screen>
42 * > POST /unauth HTTP/1.1
43 * > Soup-Debug-Timestamp: 1200171744
44 * > Soup-Debug: SoupSessionAsync 1 (0x612190), SoupMessage 1 (0x617000), SoupSocket 1 (0x612220)
46 * > Content-Type: text/plain
51 * < HTTP/1.1 201 Created
52 * < Soup-Debug-Timestamp: 1200171744
53 * < Soup-Debug: SoupMessage 1 (0x617000)
54 * < Date: Sun, 12 Jan 2008 21:02:24 GMT
55 * < Content-Length: 0
56 * </screen></informalexample>
58 * The <literal>Soup-Debug-Timestamp</literal> line gives the time (as
59 * a #time_t) when the request was sent, or the response fully
62 * The <literal>Soup-Debug</literal> line gives further debugging
63 * information about the #SoupSession, #SoupMessage, and #SoupSocket
64 * involved; the hex numbers are the addresses of the objects in
65 * question (which may be useful if you are running in a debugger).
66 * The decimal IDs are simply counters that uniquely identify objects
67 * across the lifetime of the #SoupLogger. In particular, this can be
68 * used to identify when multiple messages are sent across the same
71 * Currently, the request half of the message is logged just before
72 * the first byte of the request gets written to the network (from the
73 * #SoupSession::request_started signal), which means that if you have
74 * not made the complete request body available at that point, it will
75 * not be logged. The response is logged just after the last byte of
76 * the response body is read from the network (from the
77 * #SoupMessage::got_body or #SoupMessage::got_informational signal),
78 * which means that the #SoupMessage::got_headers signal, and anything
79 * triggered off it (such as #SoupSession::authenticate) will be
80 * emitted <emphasis>before</emphasis> the response headers are
84 static void soup_logger_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
86 static void request_queued (SoupSessionFeature *feature, SoupSession *session,
88 static void request_started (SoupSessionFeature *feature, SoupSession *session,
89 SoupMessage *msg, SoupSocket *socket);
90 static void request_unqueued (SoupSessionFeature *feature,
91 SoupSession *session, SoupMessage *msg);
93 G_DEFINE_TYPE_WITH_CODE (SoupLogger, soup_logger, G_TYPE_OBJECT,
94 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
95 soup_logger_session_feature_init))
98 /* We use a mutex so that if requests are being run in
99 * multiple threads, we don't mix up the output.
106 SoupLoggerLogLevel level;
109 SoupLoggerFilter request_filter;
110 gpointer request_filter_data;
111 GDestroyNotify request_filter_dnotify;
113 SoupLoggerFilter response_filter;
114 gpointer response_filter_data;
115 GDestroyNotify response_filter_dnotify;
117 SoupLoggerPrinter printer;
118 gpointer printer_data;
119 GDestroyNotify printer_dnotify;
121 #define SOUP_LOGGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_LOGGER, SoupLoggerPrivate))
124 soup_logger_init (SoupLogger *logger)
126 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
128 priv->lock = g_mutex_new ();
129 priv->tag = g_quark_from_static_string (g_strdup_printf ("SoupLogger-%p", logger));
130 priv->ids = g_hash_table_new (NULL, NULL);
134 finalize (GObject *object)
136 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (object);
138 g_hash_table_destroy (priv->ids);
140 if (priv->request_filter_dnotify)
141 priv->request_filter_dnotify (priv->request_filter_data);
142 if (priv->response_filter_dnotify)
143 priv->response_filter_dnotify (priv->response_filter_data);
144 if (priv->printer_dnotify)
145 priv->printer_dnotify (priv->printer_data);
147 g_mutex_free (priv->lock);
149 G_OBJECT_CLASS (soup_logger_parent_class)->finalize (object);
153 soup_logger_class_init (SoupLoggerClass *logger_class)
155 GObjectClass *object_class = G_OBJECT_CLASS (logger_class);
157 g_type_class_add_private (logger_class, sizeof (SoupLoggerPrivate));
159 object_class->finalize = finalize;
163 soup_logger_session_feature_init (SoupSessionFeatureInterface *feature_interface,
164 gpointer interface_data)
166 feature_interface->request_queued = request_queued;
167 feature_interface->request_started = request_started;
168 feature_interface->request_unqueued = request_unqueued;
172 * SoupLoggerLogLevel:
173 * @SOUP_LOGGER_LOG_NONE: No logging
174 * @SOUP_LOGGER_LOG_MINIMAL: Log the Request-Line or Status-Line and
175 * the Soup-Debug pseudo-headers
176 * @SOUP_LOGGER_LOG_HEADERS: Log the full request/response headers
177 * @SOUP_LOGGER_LOG_BODY: Log the full headers and request/response
180 * Describes the level of logging output to provide.
185 * @level: the debug level
186 * @max_body_size: the maximum body size to output, or -1
188 * Creates a new #SoupLogger with the given debug level. If @level is
189 * %SOUP_LOGGER_LOG_BODY, @max_body_size gives the maximum number of
190 * bytes of the body that will be logged. (-1 means "no limit".)
192 * If you need finer control over what message parts are and aren't
193 * logged, use soup_logger_set_request_filter() and
194 * soup_logger_set_response_filter().
196 * Returns: a new #SoupLogger
199 soup_logger_new (SoupLoggerLogLevel level, int max_body_size)
202 SoupLoggerPrivate *priv;
204 logger = g_object_new (SOUP_TYPE_LOGGER, NULL);
206 priv = SOUP_LOGGER_GET_PRIVATE (logger);
208 priv->max_body_size = max_body_size;
215 * @logger: the #SoupLogger
216 * @msg: the message being logged
217 * @user_data: the data passed to soup_logger_set_request_filter()
218 * or soup_logger_set_response_filter()
220 * The prototype for a logging filter. The filter callback will be
221 * invoked for each request or response, and should analyze it and
222 * return a #SoupLoggerLogLevel value indicating how much of the
223 * message to log. Eg, it might choose between %SOUP_LOGGER_LOG_BODY
224 * and %SOUP_LOGGER_LOG_HEADERS depending on the Content-Type.
226 * Return value: a #SoupLoggerLogLevel value indicating how much of
231 * soup_logger_set_request_filter:
232 * @logger: a #SoupLogger
233 * @request_filter: the callback for request debugging
234 * @filter_data: data to pass to the callback
235 * @destroy: a #GDestroyNotify to free @filter_data
237 * Sets up a filter to determine the log level for a given request.
238 * For each HTTP request @logger will invoke @request_filter to
239 * determine how much (if any) of that request to log. (If you do not
240 * set a request filter, @logger will just always log requests at the
241 * level passed to soup_logger_new().)
244 soup_logger_set_request_filter (SoupLogger *logger,
245 SoupLoggerFilter request_filter,
246 gpointer filter_data,
247 GDestroyNotify destroy)
249 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
251 priv->request_filter = request_filter;
252 priv->request_filter_data = filter_data;
253 priv->request_filter_dnotify = destroy;
257 * soup_logger_set_response_filter:
258 * @logger: a #SoupLogger
259 * @response_filter: the callback for response debugging
260 * @filter_data: data to pass to the callback
261 * @destroy: a #GDestroyNotify to free @filter_data
263 * Sets up a filter to determine the log level for a given response.
264 * For each HTTP response @logger will invoke @response_filter to
265 * determine how much (if any) of that response to log. (If you do not
266 * set a response filter, @logger will just always log responses at
267 * the level passed to soup_logger_new().)
270 soup_logger_set_response_filter (SoupLogger *logger,
271 SoupLoggerFilter response_filter,
272 gpointer filter_data,
273 GDestroyNotify destroy)
275 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
277 priv->response_filter = response_filter;
278 priv->response_filter_data = filter_data;
279 priv->response_filter_dnotify = destroy;
284 * @logger: the #SoupLogger
285 * @level: the level of the information being printed.
286 * @direction: a single-character prefix to @data
287 * @data: data to print
288 * @user_data: the data passed to soup_logger_set_printer()
290 * The prototype for a custom printing callback.
292 * @level indicates what kind of information is being printed. Eg, it
293 * will be %SOUP_LOGGER_LOG_HEADERS if @data is header data.
295 * @direction is either '<', '>', or ' ', and @data is the single line
296 * to print; the printer is expected to add a terminating newline.
298 * To get the effect of the default printer, you would do:
300 * <informalexample><programlisting>
301 * printf ("%c %s\n", direction, data);
302 * </programlisting></informalexample>
306 * soup_logger_set_printer:
307 * @logger: a #SoupLogger
308 * @printer: the callback for printing logging output
309 * @printer_data: data to pass to the callback
310 * @destroy: a #GDestroyNotify to free @printer_data
312 * Sets up an alternate log printing routine, if you don't want
313 * the log to go to %stdout.
316 soup_logger_set_printer (SoupLogger *logger,
317 SoupLoggerPrinter printer,
318 gpointer printer_data,
319 GDestroyNotify destroy)
321 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
323 priv->printer = printer;
324 priv->printer_data = printer_data;
325 priv->printer_dnotify = destroy;
329 soup_logger_get_id (SoupLogger *logger, gpointer object)
331 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
333 return GPOINTER_TO_UINT (g_object_get_qdata (object, priv->tag));
337 soup_logger_set_id (SoupLogger *logger, gpointer object)
339 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
340 gpointer klass = G_OBJECT_GET_CLASS (object);
343 id = g_hash_table_lookup (priv->ids, klass);
345 g_hash_table_insert (priv->ids, klass, id);
347 g_object_set_qdata (object, priv->tag, id);
348 return GPOINTER_TO_UINT (id);
352 * soup_logger_attach:
353 * @logger: a #SoupLogger
354 * @session: a #SoupSession
356 * Sets @logger to watch @session and print debug information for
359 * (The session will take a reference on @logger, which will be
360 * removed when you call soup_logger_detach(), or when the session is
363 * Deprecated: Use soup_session_add_feature() instead.
366 soup_logger_attach (SoupLogger *logger,
367 SoupSession *session)
369 soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
373 * soup_logger_detach:
374 * @logger: a #SoupLogger
375 * @session: a #SoupSession
377 * Stops @logger from watching @session.
379 * Deprecated: Use soup_session_remove_feature() instead.
382 soup_logger_detach (SoupLogger *logger,
383 SoupSession *session)
385 soup_session_remove_feature (session, SOUP_SESSION_FEATURE (logger));
389 soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level,
390 char direction, const char *format, ...)
392 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
394 char *data, *line, *end;
396 va_start (args, format);
397 data = g_strdup_vprintf (format, args);
400 if (level == SOUP_LOGGER_LOG_BODY && priv->max_body_size > 0) {
401 if (strlen (data) > priv->max_body_size + 6)
402 strcpy (data + priv->max_body_size, "\n[...]");
407 end = strchr (line, '\n');
411 priv->printer (logger, level, direction,
412 line, priv->printer_data);
414 printf ("%c %s\n", direction, line);
417 } while (end && *line);
423 soup_logger_print_basic_auth (SoupLogger *logger, const char *value)
428 decoded = (char *)g_base64_decode (value + 6, &len);
430 decoded = g_strdup (value);
431 p = strchr (decoded, ':');
433 while (++p < decoded + len)
436 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
437 "Authorization: Basic [%.*s]", len, decoded);
442 print_request (SoupLogger *logger, SoupMessage *msg,
443 SoupSession *session, SoupSocket *socket,
446 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
447 SoupLoggerLogLevel log_level;
448 SoupMessageHeadersIter iter;
449 const char *name, *value;
452 if (priv->request_filter) {
453 log_level = priv->request_filter (logger, msg,
454 priv->request_filter_data);
456 log_level = priv->level;
458 if (log_level == SOUP_LOGGER_LOG_NONE)
461 uri = soup_message_get_uri (msg);
462 if (msg->method == SOUP_METHOD_CONNECT) {
463 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
464 "CONNECT %s:%u HTTP/1.%d",
465 uri->host, uri->port,
466 soup_message_get_http_version (msg));
468 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
469 "%s %s%s%s HTTP/1.%d",
470 msg->method, uri->path,
471 uri->query ? "?" : "",
472 uri->query ? uri->query : "",
473 soup_message_get_http_version (msg));
476 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
477 "Soup-Debug-Timestamp: %lu",
478 (unsigned long)time (0));
479 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
480 "Soup-Debug: %s %u (%p), %s %u (%p), %s %u (%p)%s",
481 g_type_name_from_instance ((GTypeInstance *)session),
482 soup_logger_get_id (logger, session), session,
483 g_type_name_from_instance ((GTypeInstance *)msg),
484 soup_logger_get_id (logger, msg), msg,
485 g_type_name_from_instance ((GTypeInstance *)socket),
486 soup_logger_get_id (logger, socket), socket,
487 restarted ? ", restarted" : "");
489 if (log_level == SOUP_LOGGER_LOG_MINIMAL)
492 if (!soup_message_headers_get_one (msg->request_headers, "Host")) {
493 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
494 "Host: %s%c%u", uri->host,
495 soup_uri_uses_default_port (uri) ? '\0' : ':',
498 soup_message_headers_iter_init (&iter, msg->request_headers);
499 while (soup_message_headers_iter_next (&iter, &name, &value)) {
500 if (!g_ascii_strcasecmp (name, "Authorization") &&
501 !g_ascii_strncasecmp (value, "Basic ", 6))
502 soup_logger_print_basic_auth (logger, value);
504 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
505 "%s: %s", name, value);
508 if (log_level == SOUP_LOGGER_LOG_HEADERS)
511 if (msg->request_body->length &&
512 soup_message_body_get_accumulate (msg->request_body)) {
515 request = soup_message_body_flatten (msg->request_body);
516 g_return_if_fail (request != NULL);
517 soup_buffer_free (request);
519 if (soup_message_headers_get_expectations (msg->request_headers) != SOUP_EXPECTATION_CONTINUE) {
520 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>',
521 "\n%s", msg->request_body->data);
527 print_response (SoupLogger *logger, SoupMessage *msg)
529 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
530 SoupLoggerLogLevel log_level;
531 SoupMessageHeadersIter iter;
532 const char *name, *value;
534 if (priv->response_filter) {
535 log_level = priv->response_filter (logger, msg,
536 priv->response_filter_data);
538 log_level = priv->level;
540 if (log_level == SOUP_LOGGER_LOG_NONE)
543 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
545 soup_message_get_http_version (msg),
546 msg->status_code, msg->reason_phrase);
548 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
549 "Soup-Debug-Timestamp: %lu",
550 (unsigned long)time (0));
551 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
552 "Soup-Debug: %s %u (%p)",
553 g_type_name_from_instance ((GTypeInstance *)msg),
554 soup_logger_get_id (logger, msg), msg);
556 if (log_level == SOUP_LOGGER_LOG_MINIMAL)
559 soup_message_headers_iter_init (&iter, msg->response_headers);
560 while (soup_message_headers_iter_next (&iter, &name, &value)) {
561 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '<',
562 "%s: %s", name, value);
564 if (log_level == SOUP_LOGGER_LOG_HEADERS)
567 if (msg->response_body->data) {
568 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '<',
569 "\n%s", msg->response_body->data);
574 got_informational (SoupMessage *msg, gpointer user_data)
576 SoupLogger *logger = user_data;
577 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
579 g_mutex_lock (priv->lock);
581 print_response (logger, msg);
582 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
584 if (msg->status_code == SOUP_STATUS_CONTINUE && msg->request_body->data) {
585 SoupLoggerLogLevel log_level;
587 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
588 "[Now sending request body...]");
590 if (priv->request_filter) {
591 log_level = priv->request_filter (logger, msg,
592 priv->request_filter_data);
594 log_level = priv->level;
596 if (log_level == SOUP_LOGGER_LOG_BODY) {
597 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>',
598 "%s", msg->request_body->data);
601 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
604 g_mutex_unlock (priv->lock);
608 got_body (SoupMessage *msg, gpointer user_data)
610 SoupLogger *logger = user_data;
611 SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
613 g_mutex_lock (priv->lock);
615 print_response (logger, msg);
616 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
618 g_mutex_unlock (priv->lock);
622 request_queued (SoupSessionFeature *logger, SoupSession *session,
625 g_return_if_fail (SOUP_IS_MESSAGE (msg));
627 g_signal_connect (msg, "got-informational",
628 G_CALLBACK (got_informational),
630 g_signal_connect (msg, "got-body",
631 G_CALLBACK (got_body),
636 request_started (SoupSessionFeature *feature, SoupSession *session,
637 SoupMessage *msg, SoupSocket *socket)
639 SoupLogger *logger = SOUP_LOGGER (feature);
643 g_return_if_fail (SOUP_IS_SESSION (session));
644 g_return_if_fail (SOUP_IS_MESSAGE (msg));
645 g_return_if_fail (SOUP_IS_SOCKET (socket));
647 msg_id = soup_logger_get_id (logger, msg);
651 soup_logger_set_id (logger, msg);
655 if (!soup_logger_get_id (logger, session))
656 soup_logger_set_id (logger, session);
658 if (!soup_logger_get_id (logger, socket))
659 soup_logger_set_id (logger, socket);
661 print_request (logger, msg, session, socket, restarted);
662 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
666 request_unqueued (SoupSessionFeature *logger, SoupSession *session,
669 g_return_if_fail (SOUP_IS_MESSAGE (msg));
671 g_signal_handlers_disconnect_by_func (msg, got_informational, logger);
672 g_signal_handlers_disconnect_by_func (msg, got_body, logger);