Git init
[profile/ivi/libsoup2.4.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-session.h"
19 #include "soup-session-feature.h"
20 #include "soup-socket.h"
21 #include "soup-uri.h"
22
23 /**
24  * SECTION:soup-logger
25  * @short_description: Debug logging support
26  *
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.
31  *
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().
37  *
38  * By default, the debugging output is sent to %stdout, and looks
39  * something like:
40  *
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)
45  * > Host: localhost
46  * > Content-Type: text/plain
47  * > Connection: close
48  * > 
49  * > This is a test.
50  *   
51  * &lt; HTTP/1.1 201 Created
52  * &lt; Soup-Debug-Timestamp: 1200171744
53  * &lt; Soup-Debug: SoupMessage 1 (0x617000)
54  * &lt; Date: Sun, 12 Jan 2008 21:02:24 GMT
55  * &lt; Content-Length: 0
56  * </screen></informalexample>
57  *
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
60  * received.
61  *
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
69  * connection.
70  *
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
81  * actually logged.
82  **/
83
84 static void soup_logger_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
85
86 static void request_queued  (SoupSessionFeature *feature, SoupSession *session,
87                              SoupMessage *msg);
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);
92
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))
96
97 typedef struct {
98         /* We use a mutex so that if requests are being run in
99          * multiple threads, we don't mix up the output.
100          */
101         GMutex             *lock;
102
103         GQuark              tag;
104         GHashTable         *ids;
105
106         SoupLoggerLogLevel  level;
107         int                 max_body_size;
108
109         SoupLoggerFilter    request_filter;
110         gpointer            request_filter_data;
111         GDestroyNotify      request_filter_dnotify;
112
113         SoupLoggerFilter    response_filter;
114         gpointer            response_filter_data;
115         GDestroyNotify      response_filter_dnotify;
116
117         SoupLoggerPrinter   printer;
118         gpointer            printer_data;
119         GDestroyNotify      printer_dnotify;
120 } SoupLoggerPrivate;
121 #define SOUP_LOGGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_LOGGER, SoupLoggerPrivate))
122
123 static void
124 soup_logger_init (SoupLogger *logger)
125 {
126         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
127
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);
131 }
132
133 static void
134 finalize (GObject *object)
135 {
136         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (object);
137
138         g_hash_table_destroy (priv->ids);
139
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);
146
147         g_mutex_free (priv->lock);
148
149         G_OBJECT_CLASS (soup_logger_parent_class)->finalize (object);
150 }
151
152 static void
153 soup_logger_class_init (SoupLoggerClass *logger_class)
154 {
155         GObjectClass *object_class = G_OBJECT_CLASS (logger_class);
156
157         g_type_class_add_private (logger_class, sizeof (SoupLoggerPrivate));
158
159         object_class->finalize = finalize;
160 }
161
162 static void
163 soup_logger_session_feature_init (SoupSessionFeatureInterface *feature_interface,
164                                   gpointer interface_data)
165 {
166         feature_interface->request_queued = request_queued;
167         feature_interface->request_started = request_started;
168         feature_interface->request_unqueued = request_unqueued;
169 }
170
171 /**
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
178  * bodies.
179  *
180  * Describes the level of logging output to provide.
181  **/
182
183 /**
184  * soup_logger_new:
185  * @level: the debug level
186  * @max_body_size: the maximum body size to output, or -1
187  *
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".)
191  *
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().
195  *
196  * Returns: a new #SoupLogger
197  **/
198 SoupLogger *
199 soup_logger_new (SoupLoggerLogLevel level, int max_body_size) 
200 {
201         SoupLogger *logger;
202         SoupLoggerPrivate *priv;
203
204         logger = g_object_new (SOUP_TYPE_LOGGER, NULL);
205
206         priv = SOUP_LOGGER_GET_PRIVATE (logger);
207         priv->level = level;
208         priv->max_body_size = max_body_size;
209
210         return logger;
211 }
212
213 /**
214  * SoupLoggerFilter:
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()
219  *
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.
225  *
226  * Return value: a #SoupLoggerLogLevel value indicating how much of
227  * the message to log
228  **/
229
230 /**
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
236  *
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().)
242  **/
243 void
244 soup_logger_set_request_filter (SoupLogger       *logger,
245                                 SoupLoggerFilter  request_filter,
246                                 gpointer          filter_data,
247                                 GDestroyNotify    destroy)
248 {
249         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
250
251         priv->request_filter         = request_filter;
252         priv->request_filter_data    = filter_data;
253         priv->request_filter_dnotify = destroy;
254 }
255
256 /**
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
262  *
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().)
268  **/
269 void
270 soup_logger_set_response_filter (SoupLogger       *logger,
271                                  SoupLoggerFilter  response_filter,
272                                  gpointer          filter_data,
273                                  GDestroyNotify    destroy)
274 {
275         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
276
277         priv->response_filter         = response_filter;
278         priv->response_filter_data    = filter_data;
279         priv->response_filter_dnotify = destroy;
280 }
281
282 /**
283  * SoupLoggerPrinter:
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()
289  *
290  * The prototype for a custom printing callback.
291  *
292  * @level indicates what kind of information is being printed. Eg, it
293  * will be %SOUP_LOGGER_LOG_HEADERS if @data is header data.
294  *
295  * @direction is either '<', '>', or ' ', and @data is the single line
296  * to print; the printer is expected to add a terminating newline.
297  *
298  * To get the effect of the default printer, you would do:
299  *
300  * <informalexample><programlisting>
301  *      printf ("%c %s\n", direction, data);
302  * </programlisting></informalexample>
303  **/
304
305 /**
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
311  *
312  * Sets up an alternate log printing routine, if you don't want
313  * the log to go to %stdout.
314  **/
315 void
316 soup_logger_set_printer (SoupLogger        *logger,
317                          SoupLoggerPrinter  printer,
318                          gpointer           printer_data,
319                          GDestroyNotify     destroy)
320 {
321         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
322
323         priv->printer         = printer;
324         priv->printer_data    = printer_data;
325         priv->printer_dnotify = destroy;
326 }
327
328 static guint
329 soup_logger_get_id (SoupLogger *logger, gpointer object)
330 {
331         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
332
333         return GPOINTER_TO_UINT (g_object_get_qdata (object, priv->tag));
334 }
335
336 static guint
337 soup_logger_set_id (SoupLogger *logger, gpointer object)
338 {
339         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
340         gpointer klass = G_OBJECT_GET_CLASS (object);
341         gpointer id;
342
343         id = g_hash_table_lookup (priv->ids, klass);
344         id = (char *)id + 1;
345         g_hash_table_insert (priv->ids, klass, id);
346
347         g_object_set_qdata (object, priv->tag, id);
348         return GPOINTER_TO_UINT (id);
349 }
350
351 /**
352  * soup_logger_attach:
353  * @logger: a #SoupLogger
354  * @session: a #SoupSession
355  *
356  * Sets @logger to watch @session and print debug information for
357  * its messages.
358  *
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
361  * destroyed.)
362  *
363  * Deprecated: Use soup_session_add_feature() instead.
364  **/
365 void
366 soup_logger_attach (SoupLogger  *logger,
367                     SoupSession *session)
368 {
369         soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
370 }
371
372 /**
373  * soup_logger_detach:
374  * @logger: a #SoupLogger
375  * @session: a #SoupSession
376  *
377  * Stops @logger from watching @session.
378  *
379  * Deprecated: Use soup_session_remove_feature() instead.
380  **/
381 void
382 soup_logger_detach (SoupLogger  *logger,
383                     SoupSession *session)
384 {
385         soup_session_remove_feature (session, SOUP_SESSION_FEATURE (logger));
386 }
387
388 static void
389 soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level,
390                    char direction, const char *format, ...)
391 {
392         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
393         va_list args;
394         char *data, *line, *end;
395
396         va_start (args, format);
397         data = g_strdup_vprintf (format, args);
398         va_end (args);
399
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[...]");
403         }
404
405         line = data;
406         do {
407                 end = strchr (line, '\n');
408                 if (end)
409                         *end = '\0';
410                 if (priv->printer) {
411                         priv->printer (logger, level, direction,
412                                        line, priv->printer_data);
413                 } else
414                         printf ("%c %s\n", direction, line);
415
416                 line = end + 1;
417         } while (end && *line);
418
419         g_free (data);
420 }
421
422 static void
423 soup_logger_print_basic_auth (SoupLogger *logger, const char *value)
424 {
425         char *decoded, *p;
426         gsize len;
427
428         decoded = (char *)g_base64_decode (value + 6, &len);
429         if (!decoded)
430                 decoded = g_strdup (value);
431         p = strchr (decoded, ':');
432         if (p) {
433                 while (++p < decoded + len)
434                         *p = '*';
435         }
436         soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
437                            "Authorization: Basic [%.*s]", len, decoded);
438         g_free (decoded);
439 }
440
441 static void
442 print_request (SoupLogger *logger, SoupMessage *msg,
443                SoupSession *session, SoupSocket *socket,
444                gboolean restarted)
445 {
446         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
447         SoupLoggerLogLevel log_level;
448         SoupMessageHeadersIter iter;
449         const char *name, *value;
450         SoupURI *uri;
451
452         if (priv->request_filter) {
453                 log_level = priv->request_filter (logger, msg,
454                                                   priv->request_filter_data);
455         } else
456                 log_level = priv->level;
457
458         if (log_level == SOUP_LOGGER_LOG_NONE)
459                 return;
460
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));
467         } else {
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));
474         }
475
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" : "");
488
489         if (log_level == SOUP_LOGGER_LOG_MINIMAL)
490                 return;
491
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' : ':',
496                                    uri->port);
497         }
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);
503                 else {
504                         soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
505                                            "%s: %s", name, value);
506                 }
507         }
508         if (log_level == SOUP_LOGGER_LOG_HEADERS)
509                 return;
510
511         if (msg->request_body->length &&
512             soup_message_body_get_accumulate (msg->request_body)) {
513                 SoupBuffer *request;
514
515                 request = soup_message_body_flatten (msg->request_body);
516                 g_return_if_fail (request != NULL);
517                 soup_buffer_free (request);
518
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);
522                 }
523         }
524 }
525
526 static void
527 print_response (SoupLogger *logger, SoupMessage *msg)
528 {
529         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
530         SoupLoggerLogLevel log_level;
531         SoupMessageHeadersIter iter;
532         const char *name, *value;
533
534         if (priv->response_filter) {
535                 log_level = priv->response_filter (logger, msg,
536                                                    priv->response_filter_data);
537         } else
538                 log_level = priv->level;
539
540         if (log_level == SOUP_LOGGER_LOG_NONE)
541                 return;
542
543         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
544                            "HTTP/1.%d %u %s\n",
545                            soup_message_get_http_version (msg),
546                            msg->status_code, msg->reason_phrase);
547
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);
555
556         if (log_level == SOUP_LOGGER_LOG_MINIMAL)
557                 return;
558
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);
563         }
564         if (log_level == SOUP_LOGGER_LOG_HEADERS)
565                 return;
566
567         if (msg->response_body->data) {
568                 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '<',
569                                    "\n%s", msg->response_body->data);
570         }
571 }
572
573 static void
574 got_informational (SoupMessage *msg, gpointer user_data)
575 {
576         SoupLogger *logger = user_data;
577         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
578
579         g_mutex_lock (priv->lock);
580
581         print_response (logger, msg);
582         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
583
584         if (msg->status_code == SOUP_STATUS_CONTINUE && msg->request_body->data) {
585                 SoupLoggerLogLevel log_level;
586
587                 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
588                                    "[Now sending request body...]");
589
590                 if (priv->request_filter) {
591                         log_level = priv->request_filter (logger, msg,
592                                                           priv->request_filter_data);
593                 } else
594                         log_level = priv->level;
595
596                 if (log_level == SOUP_LOGGER_LOG_BODY) {
597                         soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>',
598                                            "%s", msg->request_body->data);
599                 }
600
601                 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
602         }
603
604         g_mutex_unlock (priv->lock);
605 }
606
607 static void
608 got_body (SoupMessage *msg, gpointer user_data)
609 {
610         SoupLogger *logger = user_data;
611         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
612
613         g_mutex_lock (priv->lock);
614
615         print_response (logger, msg);
616         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
617
618         g_mutex_unlock (priv->lock);
619 }
620
621 static void
622 request_queued (SoupSessionFeature *logger, SoupSession *session,
623                 SoupMessage *msg)
624 {
625         g_return_if_fail (SOUP_IS_MESSAGE (msg));
626
627         g_signal_connect (msg, "got-informational",
628                           G_CALLBACK (got_informational),
629                           logger);
630         g_signal_connect (msg, "got-body",
631                           G_CALLBACK (got_body),
632                           logger);
633 }
634
635 static void
636 request_started (SoupSessionFeature *feature, SoupSession *session,
637                  SoupMessage *msg, SoupSocket *socket)
638 {
639         SoupLogger *logger = SOUP_LOGGER (feature);
640         gboolean restarted;
641         guint msg_id;
642
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));
646
647         msg_id = soup_logger_get_id (logger, msg);
648         if (msg_id)
649                 restarted = TRUE;
650         else {
651                 soup_logger_set_id (logger, msg);
652                 restarted = FALSE;
653         }
654
655         if (!soup_logger_get_id (logger, session))
656                 soup_logger_set_id (logger, session);
657
658         if (!soup_logger_get_id (logger, socket))
659                 soup_logger_set_id (logger, socket);
660
661         print_request (logger, msg, session, socket, restarted);
662         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
663 }
664
665 static void
666 request_unqueued (SoupSessionFeature *logger, SoupSession *session,
667                   SoupMessage *msg)
668 {
669         g_return_if_fail (SOUP_IS_MESSAGE (msg));
670
671         g_signal_handlers_disconnect_by_func (msg, got_informational, logger);
672         g_signal_handlers_disconnect_by_func (msg, got_body, logger);
673 }