Define two new signals, request_queued and request_unqueued, to provided a
[platform/upstream/libsoup.git] / libsoup / soup-logger.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-logger.c
4  *
5  * Copyright (C) 2001-2004 Novell, Inc.
6  * Copyright (C) 2008 Red Hat, Inc.
7  */
8
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <stdio.h>
14 #include <string.h>
15
16 #include "soup-logger.h"
17 #include "soup-message.h"
18 #include "soup-uri.h"
19
20 /**
21  * SECTION:soup-logger
22  * @short_description: Debug logging support
23  *
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.
28  *
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().
34  *
35  * By default, the debugging output is sent to %stdout, and looks
36  * something like:
37  *
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)
42  * > Host: localhost
43  * > Content-Type: text/plain
44  * > Connection: close
45  * > 
46  * > This is a test.
47  *   
48  * &lt; HTTP/1.1 201 Created
49  * &lt; Soup-Debug-Timestamp: 1200171744
50  * &lt; Soup-Debug: SoupMessage 1 (0x617000)
51  * &lt; Date: Sun, 12 Jan 2008 21:02:24 GMT
52  * &lt; Content-Length: 0
53  * </screen></informalexample>
54  *
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
57  * received.
58  *
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
66  * connection.
67  **/
68
69 G_DEFINE_TYPE (SoupLogger, soup_logger, G_TYPE_OBJECT)
70
71 typedef struct {
72         /* We use a mutex so that if requests are being run in
73          * multiple threads, we don't mix up the output.
74          */
75         GMutex             *lock;
76
77         GQuark              tag;
78         GHashTable         *ids;
79
80         SoupLoggerLogLevel  level;
81         int                 max_body_size;
82
83         SoupLoggerFilter    request_filter;
84         gpointer            request_filter_data;
85         GDestroyNotify      request_filter_dnotify;
86
87         SoupLoggerFilter    response_filter;
88         gpointer            response_filter_data;
89         GDestroyNotify      response_filter_dnotify;
90
91         SoupLoggerPrinter   printer;
92         gpointer            printer_data;
93         GDestroyNotify      printer_dnotify;
94 } SoupLoggerPrivate;
95 #define SOUP_LOGGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_LOGGER, SoupLoggerPrivate))
96
97 static void
98 soup_logger_init (SoupLogger *logger)
99 {
100         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
101
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);
105 }
106
107 static void
108 finalize (GObject *object)
109 {
110         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (object);
111
112         g_hash_table_destroy (priv->ids);
113
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);
120
121         g_mutex_free (priv->lock);
122
123         G_OBJECT_CLASS (soup_logger_parent_class)->finalize (object);
124 }
125
126 static void
127 soup_logger_class_init (SoupLoggerClass *logger_class)
128 {
129         GObjectClass *object_class = G_OBJECT_CLASS (logger_class);
130
131         g_type_class_add_private (logger_class, sizeof (SoupLoggerPrivate));
132
133         object_class->finalize = finalize;
134 }
135
136 /**
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
143  * bodies.
144  *
145  * Describes the level of logging output to provide.
146  **/
147
148 /**
149  * soup_logger_new:
150  * @level: the debug level
151  * @max_body_size: the maximum body size to output, or -1
152  *
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".)
156  *
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().
160  *
161  * Returns: a new #SoupLogger
162  **/
163 SoupLogger *
164 soup_logger_new (SoupLoggerLogLevel level, int max_body_size) 
165 {
166         SoupLogger *logger;
167         SoupLoggerPrivate *priv;
168
169         logger = g_object_new (SOUP_TYPE_LOGGER, NULL);
170
171         priv = SOUP_LOGGER_GET_PRIVATE (logger);
172         priv->level = level;
173         priv->max_body_size = max_body_size;
174
175         return logger;
176 }
177
178 /**
179  * SoupLoggerFilter:
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()
184  *
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.
190  *
191  * Return value: a #SoupLoggerLogLevel value indicating how much of
192  * the message to log
193  **/
194
195 /**
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
201  *
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().)
207  **/
208 void
209 soup_logger_set_request_filter (SoupLogger       *logger,
210                                 SoupLoggerFilter  request_filter,
211                                 gpointer          filter_data,
212                                 GDestroyNotify    destroy)
213 {
214         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
215
216         priv->request_filter         = request_filter;
217         priv->request_filter_data    = filter_data;
218         priv->request_filter_dnotify = destroy;
219 }
220
221 /**
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
227  *
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().)
233  **/
234 void
235 soup_logger_set_response_filter (SoupLogger       *logger,
236                                  SoupLoggerFilter  response_filter,
237                                  gpointer          filter_data,
238                                  GDestroyNotify    destroy)
239 {
240         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
241
242         priv->response_filter         = response_filter;
243         priv->response_filter_data    = filter_data;
244         priv->response_filter_dnotify = destroy;
245 }
246
247 /**
248  * SoupLoggerPrinter:
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()
254  *
255  * The prototype for a custom printing callback.
256  *
257  * @level indicates what kind of information is being printed. Eg, it
258  * will be %SOUP_LOGGER_LOG_HEADERS if @data is header data.
259  *
260  * @direction is either '<', '>', or ' ', and @data is the single line
261  * to print; the printer is expected to add a terminating newline.
262  *
263  * To get the effect of the default printer, you would do:
264  *
265  * <informalexample><programlisting>
266  *      printf ("%c %s\n", direction, data);
267  * </programlisting></informalexample>
268  **/
269
270 /**
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
276  *
277  * Sets up an alternate log printing routine, if you don't want
278  * the log to go to %stdout.
279  **/
280 void
281 soup_logger_set_printer (SoupLogger        *logger,
282                          SoupLoggerPrinter  printer,
283                          gpointer           printer_data,
284                          GDestroyNotify     destroy)
285 {
286         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
287
288         priv->printer         = printer;
289         priv->printer_data    = printer_data;
290         priv->printer_dnotify = destroy;
291 }
292
293 static guint
294 soup_logger_get_id (SoupLogger *logger, gpointer object)
295 {
296         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
297
298         return GPOINTER_TO_UINT (g_object_get_qdata (object, priv->tag));
299 }
300
301 static guint
302 soup_logger_set_id (SoupLogger *logger, gpointer object)
303 {
304         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
305         gpointer klass = G_OBJECT_GET_CLASS (object);
306         gpointer id;
307
308         id = g_hash_table_lookup (priv->ids, klass);
309         id = (char *)id + 1;
310         g_hash_table_insert (priv->ids, klass, id);
311
312         g_object_set_qdata (object, priv->tag, id);
313         return GPOINTER_TO_UINT (id);
314 }
315
316 static void
317 soup_logger_clear_id (SoupLogger *logger, gpointer object)
318 {
319         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
320
321         g_object_set_qdata (object, priv->tag, NULL);
322 }
323
324 static void request_queued (SoupSession *session, SoupMessage *msg,
325                             gpointer user_data);
326 static void request_started (SoupSession *session, SoupMessage *msg,
327                              SoupSocket *socket, gpointer user_data);
328 static void request_unqueued (SoupSession *session, SoupMessage *msg,
329                               gpointer user_data);
330
331 static void
332 weak_notify_unref (gpointer logger, GObject *ex_session)
333 {
334         g_object_unref (logger);
335 }
336
337 /**
338  * soup_logger_attach:
339  * @logger: a #SoupLogger
340  * @session: a #SoupSession
341  *
342  * Sets @logger to watch @session and print debug information for
343  * its messages.
344  *
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
347  * destroyed.)
348  **/
349 void
350 soup_logger_attach (SoupLogger  *logger,
351                     SoupSession *session)
352 {
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);
361
362         g_object_weak_ref (G_OBJECT (session),
363                            weak_notify_unref, g_object_ref (logger));
364 }
365
366 /**
367  * soup_logger_detach:
368  * @logger: a #SoupLogger
369  * @session: a #SoupSession
370  *
371  * Stops @logger from watching @session.
372  **/
373 void
374 soup_logger_detach (SoupLogger  *logger,
375                     SoupSession *session)
376 {
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);
380
381         g_object_weak_unref (G_OBJECT (session),
382                              weak_notify_unref, logger);
383 }
384
385 static void
386 soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level,
387                    char direction, const char *format, ...)
388 {
389         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
390         va_list args;
391         char *data, *line, *end;
392
393         va_start (args, format);
394         data = g_strdup_vprintf (format, args);
395         va_end (args);
396
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[...]");
400         }
401
402         line = data;
403         do {
404                 end = strchr (line, '\n');
405                 if (end)
406                         *end = '\0';
407                 if (priv->printer) {
408                         priv->printer (logger, level, direction,
409                                        line, priv->printer_data);
410                 } else
411                         printf ("%c %s\n", direction, line);
412
413                 line = end + 1;
414         } while (end && *line);
415
416         g_free (data);
417 }
418
419 static void
420 soup_logger_print_basic_auth (SoupLogger *logger, const char *value)
421 {
422         char *decoded, *p;
423         gsize len;
424
425         decoded = (char *)g_base64_decode (value + 6, &len);
426         if (!decoded)
427                 decoded = g_strdup (value);
428         p = strchr (decoded, ':');
429         if (p) {
430                 while (++p < decoded + len)
431                         *p = '*';
432         }
433         soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
434                            "Authorization: Basic [%.*s]", len, decoded);
435         g_free (decoded);
436 }
437
438 static void
439 print_request (SoupLogger *logger, SoupMessage *msg,
440                SoupSession *session, SoupSocket *socket,
441                gboolean restarted)
442 {
443         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
444         SoupLoggerLogLevel log_level;
445         SoupMessageHeadersIter iter;
446         const char *name, *value;
447         SoupURI *uri;
448
449         if (priv->request_filter) {
450                 log_level = priv->request_filter (logger, msg,
451                                                   priv->request_filter_data);
452         } else
453                 log_level = priv->level;
454
455         if (log_level == SOUP_LOGGER_LOG_NONE)
456                 return;
457
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));
464         } else {
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));
471         }
472
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" : "");
485
486         if (log_level == SOUP_LOGGER_LOG_MINIMAL)
487                 return;
488
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);
496                 else {
497                         soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
498                                            "%s: %s", name, value);
499                 }
500         }
501         if (log_level == SOUP_LOGGER_LOG_HEADERS)
502                 return;
503
504         if (msg->request_body->length) {
505                 SoupBuffer *request;
506
507                 request = soup_message_body_flatten (msg->request_body);
508                 soup_buffer_free (request);
509
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);
513                 }
514         }
515 }
516
517 static void
518 print_response (SoupLogger *logger, SoupMessage *msg)
519 {
520         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
521         SoupLoggerLogLevel log_level;
522         SoupMessageHeadersIter iter;
523         const char *name, *value;
524
525         if (priv->response_filter) {
526                 log_level = priv->response_filter (logger, msg,
527                                                    priv->response_filter_data);
528         } else
529                 log_level = priv->level;
530
531         if (log_level == SOUP_LOGGER_LOG_NONE)
532                 return;
533
534         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
535                            "HTTP/1.%d %u %s\n",
536                            soup_message_get_http_version (msg),
537                            msg->status_code, msg->reason_phrase);
538
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);
546
547         if (log_level == SOUP_LOGGER_LOG_MINIMAL)
548                 return;
549
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);
554         }
555         if (log_level == SOUP_LOGGER_LOG_HEADERS)
556                 return;
557
558         if (msg->response_body->length) {
559                 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '<',
560                                    "\n%s", msg->response_body->data);
561         }
562 }
563
564 static void
565 got_informational (SoupMessage *msg, gpointer user_data)
566 {
567         SoupLogger *logger = user_data;
568         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
569
570         g_mutex_lock (priv->lock);
571
572         print_response (logger, msg);
573         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
574
575         if (msg->status_code == SOUP_STATUS_CONTINUE && msg->request_body->data) {
576                 SoupLoggerLogLevel log_level;
577
578                 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
579                                    "[Now sending request body...]");
580
581                 if (priv->request_filter) {
582                         log_level = priv->request_filter (logger, msg,
583                                                           priv->request_filter_data);
584                 } else
585                         log_level = priv->level;
586
587                 if (log_level == SOUP_LOGGER_LOG_BODY) {
588                         soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>',
589                                            "%s", msg->request_body->data);
590                 }
591
592                 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
593         }
594
595         g_mutex_unlock (priv->lock);
596 }
597
598 static void
599 got_body (SoupMessage *msg, gpointer user_data)
600 {
601         SoupLogger *logger = user_data;
602         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
603
604         g_mutex_lock (priv->lock);
605
606         print_response (logger, msg);
607         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
608
609         g_mutex_unlock (priv->lock);
610 }
611
612 static void
613 request_queued (SoupSession *session, SoupMessage *msg, gpointer user_data)
614 {
615         SoupLogger *logger = user_data;
616
617         g_signal_connect (msg, "got-informational",
618                           G_CALLBACK (got_informational),
619                           logger);
620         g_signal_connect (msg, "got-body",
621                           G_CALLBACK (got_body),
622                           logger);
623 }
624
625 static void
626 request_started (SoupSession *session, SoupMessage *msg,
627                  SoupSocket *socket, gpointer user_data)
628 {
629         SoupLogger *logger = user_data;
630         gboolean restarted;
631         guint msg_id;
632
633         msg_id = soup_logger_get_id (logger, msg);
634         if (msg_id)
635                 restarted = TRUE;
636         else {
637                 soup_logger_set_id (logger, msg);
638                 restarted = FALSE;
639         }
640
641         if (!soup_logger_get_id (logger, socket))
642                 soup_logger_set_id (logger, socket);
643
644         print_request (logger, msg, session, socket, restarted);
645         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
646 }
647
648 static void
649 request_unqueued (SoupSession *session, SoupMessage *msg, gpointer user_data)
650 {
651         SoupLogger *logger = user_data;
652
653         g_signal_handlers_disconnect_by_func (msg, got_informational, logger);
654         g_signal_handlers_disconnect_by_func (msg, got_body, logger);
655
656         soup_logger_clear_id (logger, msg);
657 }