soup-auth-manager: add soup_auth_manager_use_auth()
[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.h"
18
19 /**
20  * SECTION:soup-logger
21  * @short_description: Debug logging support
22  *
23  * #SoupLogger watches a #SoupSession and logs the HTTP traffic that
24  * it generates, for debugging purposes. Many applications use an
25  * environment variable to determine whether or not to use
26  * #SoupLogger, and to determine the amount of debugging output.
27  *
28  * To use #SoupLogger, first create a logger with soup_logger_new(),
29  * optionally configure it with soup_logger_set_request_filter(),
30  * soup_logger_set_response_filter(), and soup_logger_set_printer(),
31  * and then attach it to a session (or multiple sessions) with
32  * soup_session_add_feature().
33  *
34  * By default, the debugging output is sent to
35  * <literal>stdout</literal>, and looks something like:
36  *
37  * <informalexample><screen>
38  * > POST /unauth HTTP/1.1
39  * > Soup-Debug-Timestamp: 1200171744
40  * > Soup-Debug: SoupSessionAsync 1 (0x612190), SoupMessage 1 (0x617000), SoupSocket 1 (0x612220)
41  * > Host: localhost
42  * > Content-Type: text/plain
43  * > Connection: close
44  * > 
45  * > This is a test.
46  *   
47  * &lt; HTTP/1.1 201 Created
48  * &lt; Soup-Debug-Timestamp: 1200171744
49  * &lt; Soup-Debug: SoupMessage 1 (0x617000)
50  * &lt; Date: Sun, 12 Jan 2008 21:02:24 GMT
51  * &lt; Content-Length: 0
52  * </screen></informalexample>
53  *
54  * The <literal>Soup-Debug-Timestamp</literal> line gives the time (as
55  * a <type>time_t</type>) when the request was sent, or the response fully
56  * received.
57  *
58  * The <literal>Soup-Debug</literal> line gives further debugging
59  * information about the #SoupSession, #SoupMessage, and #SoupSocket
60  * involved; the hex numbers are the addresses of the objects in
61  * question (which may be useful if you are running in a debugger).
62  * The decimal IDs are simply counters that uniquely identify objects
63  * across the lifetime of the #SoupLogger. In particular, this can be
64  * used to identify when multiple messages are sent across the same
65  * connection.
66  *
67  * Currently, the request half of the message is logged just before
68  * the first byte of the request gets written to the network (from the
69  * #SoupSession::request_started signal), which means that if you have
70  * not made the complete request body available at that point, it will
71  * not be logged. The response is logged just after the last byte of
72  * the response body is read from the network (from the
73  * #SoupMessage::got_body or #SoupMessage::got_informational signal),
74  * which means that the #SoupMessage::got_headers signal, and anything
75  * triggered off it (such as #SoupSession::authenticate) will be
76  * emitted <emphasis>before</emphasis> the response headers are
77  * actually logged.
78  **/
79
80 static void soup_logger_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
81
82 G_DEFINE_TYPE_WITH_CODE (SoupLogger, soup_logger, G_TYPE_OBJECT,
83                          G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
84                                                 soup_logger_session_feature_init))
85
86 typedef struct {
87         /* We use a mutex so that if requests are being run in
88          * multiple threads, we don't mix up the output.
89          */
90         GMutex             lock;
91
92         GQuark              tag;
93         GHashTable         *ids;
94
95         SoupLoggerLogLevel  level;
96         int                 max_body_size;
97
98         SoupLoggerFilter    request_filter;
99         gpointer            request_filter_data;
100         GDestroyNotify      request_filter_dnotify;
101
102         SoupLoggerFilter    response_filter;
103         gpointer            response_filter_data;
104         GDestroyNotify      response_filter_dnotify;
105
106         SoupLoggerPrinter   printer;
107         gpointer            printer_data;
108         GDestroyNotify      printer_dnotify;
109 } SoupLoggerPrivate;
110 #define SOUP_LOGGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_LOGGER, SoupLoggerPrivate))
111
112 static void
113 soup_logger_init (SoupLogger *logger)
114 {
115         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
116
117         g_mutex_init (&priv->lock);
118         priv->tag = g_quark_from_static_string (g_strdup_printf ("SoupLogger-%p", logger));
119         priv->ids = g_hash_table_new (NULL, NULL);
120 }
121
122 static void
123 soup_logger_finalize (GObject *object)
124 {
125         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (object);
126
127         g_hash_table_destroy (priv->ids);
128
129         if (priv->request_filter_dnotify)
130                 priv->request_filter_dnotify (priv->request_filter_data);
131         if (priv->response_filter_dnotify)
132                 priv->response_filter_dnotify (priv->response_filter_data);
133         if (priv->printer_dnotify)
134                 priv->printer_dnotify (priv->printer_data);
135
136         g_mutex_clear (&priv->lock);
137
138         G_OBJECT_CLASS (soup_logger_parent_class)->finalize (object);
139 }
140
141 static void
142 soup_logger_class_init (SoupLoggerClass *logger_class)
143 {
144         GObjectClass *object_class = G_OBJECT_CLASS (logger_class);
145
146         g_type_class_add_private (logger_class, sizeof (SoupLoggerPrivate));
147
148         object_class->finalize = soup_logger_finalize;
149 }
150
151 /**
152  * SoupLoggerLogLevel:
153  * @SOUP_LOGGER_LOG_NONE: No logging
154  * @SOUP_LOGGER_LOG_MINIMAL: Log the Request-Line or Status-Line and
155  * the Soup-Debug pseudo-headers
156  * @SOUP_LOGGER_LOG_HEADERS: Log the full request/response headers
157  * @SOUP_LOGGER_LOG_BODY: Log the full headers and request/response
158  * bodies.
159  *
160  * Describes the level of logging output to provide.
161  **/
162
163 /**
164  * soup_logger_new:
165  * @level: the debug level
166  * @max_body_size: the maximum body size to output, or -1
167  *
168  * Creates a new #SoupLogger with the given debug level. If @level is
169  * %SOUP_LOGGER_LOG_BODY, @max_body_size gives the maximum number of
170  * bytes of the body that will be logged. (-1 means "no limit".)
171  *
172  * If you need finer control over what message parts are and aren't
173  * logged, use soup_logger_set_request_filter() and
174  * soup_logger_set_response_filter().
175  *
176  * Returns: a new #SoupLogger
177  **/
178 SoupLogger *
179 soup_logger_new (SoupLoggerLogLevel level, int max_body_size) 
180 {
181         SoupLogger *logger;
182         SoupLoggerPrivate *priv;
183
184         logger = g_object_new (SOUP_TYPE_LOGGER, NULL);
185
186         priv = SOUP_LOGGER_GET_PRIVATE (logger);
187         priv->level = level;
188         priv->max_body_size = max_body_size;
189
190         return logger;
191 }
192
193 /**
194  * SoupLoggerFilter:
195  * @logger: the #SoupLogger
196  * @msg: the message being logged
197  * @user_data: the data passed to soup_logger_set_request_filter()
198  * or soup_logger_set_response_filter()
199  *
200  * The prototype for a logging filter. The filter callback will be
201  * invoked for each request or response, and should analyze it and
202  * return a #SoupLoggerLogLevel value indicating how much of the
203  * message to log. Eg, it might choose between %SOUP_LOGGER_LOG_BODY
204  * and %SOUP_LOGGER_LOG_HEADERS depending on the Content-Type.
205  *
206  * Return value: a #SoupLoggerLogLevel value indicating how much of
207  * the message to log
208  **/
209
210 /**
211  * soup_logger_set_request_filter:
212  * @logger: a #SoupLogger
213  * @request_filter: the callback for request debugging
214  * @filter_data: data to pass to the callback
215  * @destroy: a #GDestroyNotify to free @filter_data
216  *
217  * Sets up a filter to determine the log level for a given request.
218  * For each HTTP request @logger will invoke @request_filter to
219  * determine how much (if any) of that request to log. (If you do not
220  * set a request filter, @logger will just always log requests at the
221  * level passed to soup_logger_new().)
222  **/
223 void
224 soup_logger_set_request_filter (SoupLogger       *logger,
225                                 SoupLoggerFilter  request_filter,
226                                 gpointer          filter_data,
227                                 GDestroyNotify    destroy)
228 {
229         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
230
231         priv->request_filter         = request_filter;
232         priv->request_filter_data    = filter_data;
233         priv->request_filter_dnotify = destroy;
234 }
235
236 /**
237  * soup_logger_set_response_filter:
238  * @logger: a #SoupLogger
239  * @response_filter: the callback for response debugging
240  * @filter_data: data to pass to the callback
241  * @destroy: a #GDestroyNotify to free @filter_data
242  *
243  * Sets up a filter to determine the log level for a given response.
244  * For each HTTP response @logger will invoke @response_filter to
245  * determine how much (if any) of that response to log. (If you do not
246  * set a response filter, @logger will just always log responses at
247  * the level passed to soup_logger_new().)
248  **/
249 void
250 soup_logger_set_response_filter (SoupLogger       *logger,
251                                  SoupLoggerFilter  response_filter,
252                                  gpointer          filter_data,
253                                  GDestroyNotify    destroy)
254 {
255         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
256
257         priv->response_filter         = response_filter;
258         priv->response_filter_data    = filter_data;
259         priv->response_filter_dnotify = destroy;
260 }
261
262 /**
263  * SoupLoggerPrinter:
264  * @logger: the #SoupLogger
265  * @level: the level of the information being printed.
266  * @direction: a single-character prefix to @data
267  * @data: data to print
268  * @user_data: the data passed to soup_logger_set_printer()
269  *
270  * The prototype for a custom printing callback.
271  *
272  * @level indicates what kind of information is being printed. Eg, it
273  * will be %SOUP_LOGGER_LOG_HEADERS if @data is header data.
274  *
275  * @direction is either '<', '>', or ' ', and @data is the single line
276  * to print; the printer is expected to add a terminating newline.
277  *
278  * To get the effect of the default printer, you would do:
279  *
280  * <informalexample><programlisting>
281  *      printf ("%c %s\n", direction, data);
282  * </programlisting></informalexample>
283  **/
284
285 /**
286  * soup_logger_set_printer:
287  * @logger: a #SoupLogger
288  * @printer: the callback for printing logging output
289  * @printer_data: data to pass to the callback
290  * @destroy: a #GDestroyNotify to free @printer_data
291  *
292  * Sets up an alternate log printing routine, if you don't want
293  * the log to go to <literal>stdout</literal>.
294  **/
295 void
296 soup_logger_set_printer (SoupLogger        *logger,
297                          SoupLoggerPrinter  printer,
298                          gpointer           printer_data,
299                          GDestroyNotify     destroy)
300 {
301         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
302
303         priv->printer         = printer;
304         priv->printer_data    = printer_data;
305         priv->printer_dnotify = destroy;
306 }
307
308 static guint
309 soup_logger_get_id (SoupLogger *logger, gpointer object)
310 {
311         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
312
313         return GPOINTER_TO_UINT (g_object_get_qdata (object, priv->tag));
314 }
315
316 static guint
317 soup_logger_set_id (SoupLogger *logger, gpointer object)
318 {
319         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
320         gpointer klass = G_OBJECT_GET_CLASS (object);
321         gpointer id;
322
323         id = g_hash_table_lookup (priv->ids, klass);
324         id = (char *)id + 1;
325         g_hash_table_insert (priv->ids, klass, id);
326
327         g_object_set_qdata (object, priv->tag, id);
328         return GPOINTER_TO_UINT (id);
329 }
330
331 /**
332  * soup_logger_attach:
333  * @logger: a #SoupLogger
334  * @session: a #SoupSession
335  *
336  * Sets @logger to watch @session and print debug information for
337  * its messages.
338  *
339  * (The session will take a reference on @logger, which will be
340  * removed when you call soup_logger_detach(), or when the session is
341  * destroyed.)
342  *
343  * Deprecated: Use soup_session_add_feature() instead.
344  **/
345 void
346 soup_logger_attach (SoupLogger  *logger,
347                     SoupSession *session)
348 {
349         soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
350 }
351
352 /**
353  * soup_logger_detach:
354  * @logger: a #SoupLogger
355  * @session: a #SoupSession
356  *
357  * Stops @logger from watching @session.
358  *
359  * Deprecated: Use soup_session_remove_feature() instead.
360  **/
361 void
362 soup_logger_detach (SoupLogger  *logger,
363                     SoupSession *session)
364 {
365         soup_session_remove_feature (session, SOUP_SESSION_FEATURE (logger));
366 }
367
368 static void
369 soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level,
370                    char direction, const char *format, ...)
371 {
372         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
373         va_list args;
374         char *data, *line, *end;
375
376         va_start (args, format);
377         data = g_strdup_vprintf (format, args);
378         va_end (args);
379
380         if (level == SOUP_LOGGER_LOG_BODY && priv->max_body_size > 0) {
381                 if (strlen (data) > priv->max_body_size + 6)
382                         strcpy (data + priv->max_body_size, "\n[...]");
383         }
384
385         line = data;
386         do {
387                 end = strchr (line, '\n');
388                 if (end)
389                         *end = '\0';
390                 if (priv->printer) {
391                         priv->printer (logger, level, direction,
392                                        line, priv->printer_data);
393                 } else
394                         printf ("%c %s\n", direction, line);
395
396                 line = end + 1;
397         } while (end && *line);
398
399         g_free (data);
400 }
401
402 static void
403 soup_logger_print_basic_auth (SoupLogger *logger, const char *value)
404 {
405         char *decoded, *decoded_utf8, *p;
406         gsize len;
407
408         decoded = (char *)g_base64_decode (value + 6, &len);
409         if (decoded && !g_utf8_validate (decoded, -1, NULL)) {
410                 decoded_utf8 = g_convert_with_fallback (decoded, -1,
411                                                         "UTF-8", "ISO-8859-1",
412                                                         NULL, NULL, &len,
413                                                         NULL);
414                 if (decoded_utf8) {
415                         g_free (decoded);
416                         decoded = decoded_utf8;
417                 }
418         }
419
420         if (!decoded)
421                 decoded = g_strdup (value);
422         p = strchr (decoded, ':');
423         if (p) {
424                 while (++p < decoded + len)
425                         *p = '*';
426         }
427         soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
428                            "Authorization: Basic [%.*s]", len, decoded);
429         g_free (decoded);
430 }
431
432 static void
433 print_request (SoupLogger *logger, SoupMessage *msg,
434                SoupSession *session, SoupSocket *socket,
435                gboolean restarted)
436 {
437         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
438         SoupLoggerLogLevel log_level;
439         SoupMessageHeadersIter iter;
440         const char *name, *value;
441         SoupURI *uri;
442
443         if (priv->request_filter) {
444                 log_level = priv->request_filter (logger, msg,
445                                                   priv->request_filter_data);
446         } else
447                 log_level = priv->level;
448
449         if (log_level == SOUP_LOGGER_LOG_NONE)
450                 return;
451
452         uri = soup_message_get_uri (msg);
453         if (msg->method == SOUP_METHOD_CONNECT) {
454                 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
455                                    "CONNECT %s:%u HTTP/1.%d",
456                                    uri->host, uri->port,
457                                    soup_message_get_http_version (msg));
458         } else {
459                 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
460                                    "%s %s%s%s HTTP/1.%d",
461                                    msg->method, uri->path,
462                                    uri->query ? "?" : "",
463                                    uri->query ? uri->query : "",
464                                    soup_message_get_http_version (msg));
465         }
466
467         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
468                            "Soup-Debug-Timestamp: %lu",
469                            (unsigned long)time (0));
470         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
471                            "Soup-Debug: %s %u (%p), %s %u (%p), %s %u (%p)%s",
472                            g_type_name_from_instance ((GTypeInstance *)session),
473                            soup_logger_get_id (logger, session), session,
474                            g_type_name_from_instance ((GTypeInstance *)msg),
475                            soup_logger_get_id (logger, msg), msg,
476                            g_type_name_from_instance ((GTypeInstance *)socket),
477                            soup_logger_get_id (logger, socket), socket,
478                            restarted ? ", restarted" : "");
479
480         if (log_level == SOUP_LOGGER_LOG_MINIMAL)
481                 return;
482
483         if (!soup_message_headers_get_one (msg->request_headers, "Host")) {
484                 char *uri_host;
485
486                 if (strchr (uri->host, ':'))
487                         uri_host = g_strdup_printf ("[%s]", uri->host);
488                 else if (g_hostname_is_non_ascii (uri->host))
489                         uri_host = g_hostname_to_ascii (uri->host);
490                 else
491                         uri_host = uri->host;
492
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                 if (uri_host != uri->host)
499                         g_free (uri_host);
500         }
501         soup_message_headers_iter_init (&iter, msg->request_headers);
502         while (soup_message_headers_iter_next (&iter, &name, &value)) {
503                 if (!g_ascii_strcasecmp (name, "Authorization") &&
504                     !g_ascii_strncasecmp (value, "Basic ", 6))
505                         soup_logger_print_basic_auth (logger, value);
506                 else {
507                         soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>',
508                                            "%s: %s", name, value);
509                 }
510         }
511         if (log_level == SOUP_LOGGER_LOG_HEADERS)
512                 return;
513
514         if (msg->request_body->length &&
515             soup_message_body_get_accumulate (msg->request_body)) {
516                 SoupBuffer *request;
517
518                 request = soup_message_body_flatten (msg->request_body);
519                 g_return_if_fail (request != NULL);
520                 soup_buffer_free (request);
521
522                 if (soup_message_headers_get_expectations (msg->request_headers) != SOUP_EXPECTATION_CONTINUE) {
523                         soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>',
524                                            "\n%s", msg->request_body->data);
525                 }
526         }
527 }
528
529 static void
530 print_response (SoupLogger *logger, SoupMessage *msg)
531 {
532         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
533         SoupLoggerLogLevel log_level;
534         SoupMessageHeadersIter iter;
535         const char *name, *value;
536
537         if (priv->response_filter) {
538                 log_level = priv->response_filter (logger, msg,
539                                                    priv->response_filter_data);
540         } else
541                 log_level = priv->level;
542
543         if (log_level == SOUP_LOGGER_LOG_NONE)
544                 return;
545
546         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
547                            "HTTP/1.%d %u %s\n",
548                            soup_message_get_http_version (msg),
549                            msg->status_code, msg->reason_phrase);
550
551         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
552                            "Soup-Debug-Timestamp: %lu",
553                            (unsigned long)time (0));
554         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '<',
555                            "Soup-Debug: %s %u (%p)",
556                            g_type_name_from_instance ((GTypeInstance *)msg),
557                            soup_logger_get_id (logger, msg), msg);
558
559         if (log_level == SOUP_LOGGER_LOG_MINIMAL)
560                 return;
561
562         soup_message_headers_iter_init (&iter, msg->response_headers);
563         while (soup_message_headers_iter_next (&iter, &name, &value)) {
564                 soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '<',
565                                    "%s: %s", name, value);
566         }
567         if (log_level == SOUP_LOGGER_LOG_HEADERS)
568                 return;
569
570         if (msg->response_body->data) {
571                 soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '<',
572                                    "\n%s", msg->response_body->data);
573         }
574 }
575
576 static void
577 got_informational (SoupMessage *msg, gpointer user_data)
578 {
579         SoupLogger *logger = user_data;
580         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
581
582         g_mutex_lock (&priv->lock);
583
584         print_response (logger, msg);
585         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
586
587         if (msg->status_code == SOUP_STATUS_CONTINUE && msg->request_body->data) {
588                 SoupLoggerLogLevel log_level;
589
590                 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
591                                    "[Now sending request body...]");
592
593                 if (priv->request_filter) {
594                         log_level = priv->request_filter (logger, msg,
595                                                           priv->request_filter_data);
596                 } else
597                         log_level = priv->level;
598
599                 if (log_level == SOUP_LOGGER_LOG_BODY) {
600                         soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>',
601                                            "%s", msg->request_body->data);
602                 }
603
604                 soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
605         }
606
607         g_mutex_unlock (&priv->lock);
608 }
609
610 static void
611 got_body (SoupMessage *msg, gpointer user_data)
612 {
613         SoupLogger *logger = user_data;
614         SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger);
615
616         g_mutex_lock (&priv->lock);
617
618         print_response (logger, msg);
619         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
620
621         g_mutex_unlock (&priv->lock);
622 }
623
624 static void
625 soup_logger_request_queued (SoupSessionFeature *logger,
626                             SoupSession *session,
627                             SoupMessage *msg)
628 {
629         g_return_if_fail (SOUP_IS_MESSAGE (msg));
630
631         g_signal_connect (msg, "got-informational",
632                           G_CALLBACK (got_informational),
633                           logger);
634         g_signal_connect (msg, "got-body",
635                           G_CALLBACK (got_body),
636                           logger);
637 }
638
639 static void
640 soup_logger_request_started (SoupSessionFeature *feature,
641                              SoupSession *session,
642                              SoupMessage *msg,
643                              SoupSocket *socket)
644 {
645         SoupLogger *logger = SOUP_LOGGER (feature);
646         gboolean restarted;
647         guint msg_id;
648
649         g_return_if_fail (SOUP_IS_SESSION (session));
650         g_return_if_fail (SOUP_IS_MESSAGE (msg));
651         g_return_if_fail (SOUP_IS_SOCKET (socket));
652
653         msg_id = soup_logger_get_id (logger, msg);
654         if (msg_id)
655                 restarted = TRUE;
656         else {
657                 soup_logger_set_id (logger, msg);
658                 restarted = FALSE;
659         }
660
661         if (!soup_logger_get_id (logger, session))
662                 soup_logger_set_id (logger, session);
663
664         if (!soup_logger_get_id (logger, socket))
665                 soup_logger_set_id (logger, socket);
666
667         print_request (logger, msg, session, socket, restarted);
668         soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "");
669 }
670
671 static void
672 soup_logger_request_unqueued (SoupSessionFeature *logger,
673                               SoupSession *session,
674                               SoupMessage *msg)
675 {
676         g_return_if_fail (SOUP_IS_MESSAGE (msg));
677
678         g_signal_handlers_disconnect_by_func (msg, got_informational, logger);
679         g_signal_handlers_disconnect_by_func (msg, got_body, logger);
680 }
681
682 static void
683 soup_logger_session_feature_init (SoupSessionFeatureInterface *feature_interface,
684                                   gpointer interface_data)
685 {
686         feature_interface->request_queued = soup_logger_request_queued;
687         feature_interface->request_started = soup_logger_request_started;
688         feature_interface->request_unqueued = soup_logger_request_unqueued;
689 }