2 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (c) 1996-1999 by Internet Software Consortium.
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
15 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #if !defined(LINT) && !defined(CODECENTER)
19 static const char rcsid
[] = "$Id: logging.c,v 1.7.672.1 2008/02/28 05:46:12 marka Exp $";
22 #include "port_before.h"
24 #include <sys/types.h>
39 #include <isc/assertions.h>
40 #include <isc/logging.h>
41 #include <isc/memcluster.h>
44 #include "port_after.h"
46 #include "logging_p.h"
48 static const int syslog_priority
[] = { LOG_DEBUG
, LOG_INFO
, LOG_NOTICE
,
49 LOG_WARNING
, LOG_ERR
, LOG_CRIT
};
51 static const char *months
[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
52 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
54 static const char *level_text
[] = {
55 "info: ", "notice: ", "warning: ", "error: ", "critical: "
59 version_rename(log_channel chan
) {
61 char old_name
[PATH_MAX
+1];
62 char new_name
[PATH_MAX
+1];
64 ver
= chan
->out
.file
.versions
;
67 if (ver
> LOG_MAX_VERSIONS
)
68 ver
= LOG_MAX_VERSIONS
;
70 * Need to have room for '.nn' (XXX assumes LOG_MAX_VERSIONS < 100)
72 if (strlen(chan
->out
.file
.name
) > (size_t)(PATH_MAX
-3))
74 for (ver
--; ver
> 0; ver
--) {
75 sprintf(old_name
, "%s.%d", chan
->out
.file
.name
, ver
-1);
76 sprintf(new_name
, "%s.%d", chan
->out
.file
.name
, ver
);
77 (void)isc_movefile(old_name
, new_name
);
79 sprintf(new_name
, "%s.0", chan
->out
.file
.name
);
80 (void)isc_movefile(chan
->out
.file
.name
, new_name
);
84 log_open_stream(log_channel chan
) {
90 if (chan
== NULL
|| chan
->type
!= log_file
) {
96 * Don't open already open streams
98 if (chan
->out
.file
.stream
!= NULL
)
99 return (chan
->out
.file
.stream
);
101 if (stat(chan
->out
.file
.name
, &sb
) < 0) {
102 if (errno
!= ENOENT
) {
104 "log_open_stream: stat of %s failed: %s",
105 chan
->out
.file
.name
, strerror(errno
));
106 chan
->flags
|= LOG_CHANNEL_BROKEN
;
111 regular
= (sb
.st_mode
& S_IFREG
);
113 if (chan
->out
.file
.versions
) {
116 "log_open_stream: want versions but %s isn't a regular file",
117 chan
->out
.file
.name
);
118 chan
->flags
|= LOG_CHANNEL_BROKEN
;
124 flags
= O_WRONLY
|O_CREAT
|O_APPEND
;
126 if ((chan
->flags
& LOG_TRUNCATE
) != 0) {
128 (void)unlink(chan
->out
.file
.name
);
132 "log_open_stream: want truncation but %s isn't a regular file",
133 chan
->out
.file
.name
);
134 chan
->flags
|= LOG_CHANNEL_BROKEN
;
140 fd
= open(chan
->out
.file
.name
, flags
,
141 S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
|S_IROTH
|S_IWOTH
);
143 syslog(LOG_ERR
, "log_open_stream: open(%s) failed: %s",
144 chan
->out
.file
.name
, strerror(errno
));
145 chan
->flags
|= LOG_CHANNEL_BROKEN
;
148 stream
= fdopen(fd
, "a");
149 if (stream
== NULL
) {
150 syslog(LOG_ERR
, "log_open_stream: fdopen() failed");
151 chan
->flags
|= LOG_CHANNEL_BROKEN
;
154 (void) fchown(fd
, chan
->out
.file
.owner
, chan
->out
.file
.group
);
156 chan
->out
.file
.stream
= stream
;
161 log_close_stream(log_channel chan
) {
164 if (chan
== NULL
|| chan
->type
!= log_file
) {
168 stream
= chan
->out
.file
.stream
;
169 chan
->out
.file
.stream
= NULL
;
170 if (stream
!= NULL
&& fclose(stream
) == EOF
)
176 log_close_debug_channels(log_context lc
) {
177 log_channel_list lcl
;
180 for (i
= 0; i
< lc
->num_categories
; i
++)
181 for (lcl
= lc
->categories
[i
]; lcl
!= NULL
; lcl
= lcl
->next
)
182 if (lcl
->channel
->type
== log_file
&&
183 lcl
->channel
->out
.file
.stream
!= NULL
&&
184 lcl
->channel
->flags
& LOG_REQUIRE_DEBUG
)
185 (void)log_close_stream(lcl
->channel
);
189 log_get_stream(log_channel chan
) {
190 if (chan
== NULL
|| chan
->type
!= log_file
) {
194 return (chan
->out
.file
.stream
);
198 log_get_filename(log_channel chan
) {
199 if (chan
== NULL
|| chan
->type
!= log_file
) {
203 return (chan
->out
.file
.name
);
207 log_check_channel(log_context lc
, int level
, log_channel chan
) {
208 int debugging
, chan_level
;
212 debugging
= ((lc
->flags
& LOG_OPTION_DEBUG
) != 0);
215 * If not debugging, short circuit debugging messages very early.
217 if (level
> 0 && !debugging
)
220 if ((chan
->flags
& (LOG_CHANNEL_BROKEN
|LOG_CHANNEL_OFF
)) != 0)
223 /* Some channels only log when debugging is on. */
224 if ((chan
->flags
& LOG_REQUIRE_DEBUG
) && !debugging
)
227 /* Some channels use the global level. */
228 if ((chan
->flags
& LOG_USE_CONTEXT_LEVEL
) != 0) {
229 chan_level
= lc
->level
;
231 chan_level
= chan
->level
;
233 if (level
> chan_level
)
240 log_check(log_context lc
, int category
, int level
) {
241 log_channel_list lcl
;
246 debugging
= ((lc
->flags
& LOG_OPTION_DEBUG
) != 0);
249 * If not debugging, short circuit debugging messages very early.
251 if (level
> 0 && !debugging
)
254 if (category
< 0 || category
> lc
->num_categories
)
255 category
= 0; /*%< use default */
256 lcl
= lc
->categories
[category
];
259 lcl
= lc
->categories
[0];
262 for ( /* nothing */; lcl
!= NULL
; lcl
= lcl
->next
) {
263 if (log_check_channel(lc
, level
, lcl
->channel
))
270 log_vwrite(log_context lc
, int category
, int level
, const char *format
,
272 log_channel_list lcl
;
273 int pri
, debugging
, did_vsprintf
= 0;
274 int original_category
;
283 const char *category_name
;
284 const char *level_str
;
290 debugging
= (lc
->flags
& LOG_OPTION_DEBUG
);
293 * If not debugging, short circuit debugging messages very early.
295 if (level
> 0 && !debugging
)
298 if (category
< 0 || category
> lc
->num_categories
)
299 category
= 0; /*%< use default */
300 original_category
= category
;
301 lcl
= lc
->categories
[category
];
304 lcl
= lc
->categories
[0];
308 * Get the current time and format it.
311 if (gettimeofday(&tv
, NULL
) < 0) {
312 syslog(LOG_INFO
, "gettimeofday failed in log_vwrite()");
316 local_tm
= localtime_r(&tt
, &tm_tmp
);
318 local_tm
= localtime(&tt
);
320 if (local_tm
!= NULL
) {
321 sprintf(time_buf
, "%02d-%s-%4d %02d:%02d:%02d.%03ld ",
322 local_tm
->tm_mday
, months
[local_tm
->tm_mon
],
323 local_tm
->tm_year
+1900, local_tm
->tm_hour
,
324 local_tm
->tm_min
, local_tm
->tm_sec
,
325 (long)tv
.tv_usec
/1000);
330 * Make a string representation of the current category and level
333 if (lc
->category_names
!= NULL
&&
334 lc
->category_names
[original_category
] != NULL
)
335 category_name
= lc
->category_names
[original_category
];
339 if (level
>= log_critical
) {
341 sprintf(level_buf
, "debug %d: ", level
);
342 level_str
= level_buf
;
344 level_str
= level_text
[-level
-1];
346 sprintf(level_buf
, "level %d: ", level
);
347 level_str
= level_buf
;
351 * Write the message to channels.
353 for ( /* nothing */; lcl
!= NULL
; lcl
= lcl
->next
) {
356 if (!log_check_channel(lc
, level
, chan
))
360 (void)vsprintf(lc
->buffer
, format
, args
);
361 if (strlen(lc
->buffer
) > (size_t)LOG_BUFFER_SIZE
) {
363 "memory overrun in log_vwrite()");
369 switch (chan
->type
) {
371 if (level
>= log_critical
)
372 pri
= (level
>= 0) ? 0 : -level
;
375 syslog(chan
->out
.facility
|syslog_priority
[pri
],
377 (chan
->flags
& LOG_TIMESTAMP
) ? time_buf
: "",
378 (chan
->flags
& LOG_PRINT_CATEGORY
) ?
380 (chan
->flags
& LOG_PRINT_LEVEL
) ?
385 stream
= chan
->out
.file
.stream
;
386 if (stream
== NULL
) {
387 stream
= log_open_stream(chan
);
391 if (chan
->out
.file
.max_size
!= ULONG_MAX
) {
397 chan
->out
.file
.max_size
) {
399 * try to roll over the log files,
400 * ignoring all all return codes
401 * except the open (we don't want
402 * to write any more anyway)
404 log_close_stream(chan
);
405 version_rename(chan
);
406 stream
= log_open_stream(chan
);
411 fprintf(stream
, "%s%s%s%s\n",
412 (chan
->flags
& LOG_TIMESTAMP
) ? time_buf
: "",
413 (chan
->flags
& LOG_PRINT_CATEGORY
) ?
415 (chan
->flags
& LOG_PRINT_LEVEL
) ?
424 "unknown channel type in log_vwrite()");
430 log_write(log_context lc
, int category
, int level
, const char *format
, ...) {
433 va_start(args
, format
);
434 log_vwrite(lc
, category
, level
, format
, args
);
439 * Functions to create, set, or destroy contexts
443 log_new_context(int num_categories
, char **category_names
, log_context
*lc
) {
446 nlc
= memget(sizeof (struct log_context
));
451 nlc
->num_categories
= num_categories
;
452 nlc
->category_names
= category_names
;
453 nlc
->categories
= memget(num_categories
* sizeof (log_channel_list
));
454 if (nlc
->categories
== NULL
) {
455 memput(nlc
, sizeof (struct log_context
));
459 memset(nlc
->categories
, '\0',
460 num_categories
* sizeof (log_channel_list
));
468 log_free_context(log_context lc
) {
469 log_channel_list lcl
, lcl_next
;
475 for (i
= 0; i
< lc
->num_categories
; i
++)
476 for (lcl
= lc
->categories
[i
]; lcl
!= NULL
; lcl
= lcl_next
) {
477 lcl_next
= lcl
->next
;
479 (void)log_free_channel(chan
);
480 memput(lcl
, sizeof (struct log_channel_list
));
482 memput(lc
->categories
,
483 lc
->num_categories
* sizeof (log_channel_list
));
484 memput(lc
, sizeof (struct log_context
));
488 log_add_channel(log_context lc
, int category
, log_channel chan
) {
489 log_channel_list lcl
;
491 if (lc
== NULL
|| category
< 0 || category
>= lc
->num_categories
) {
496 lcl
= memget(sizeof (struct log_channel_list
));
502 lcl
->next
= lc
->categories
[category
];
503 lc
->categories
[category
] = lcl
;
509 log_remove_channel(log_context lc
, int category
, log_channel chan
) {
510 log_channel_list lcl
, prev_lcl
, next_lcl
;
513 if (lc
== NULL
|| category
< 0 || category
>= lc
->num_categories
) {
518 for (prev_lcl
= NULL
, lcl
= lc
->categories
[category
];
521 next_lcl
= lcl
->next
;
522 if (lcl
->channel
== chan
) {
523 log_free_channel(chan
);
524 if (prev_lcl
!= NULL
)
525 prev_lcl
->next
= next_lcl
;
527 lc
->categories
[category
] = next_lcl
;
528 memput(lcl
, sizeof (struct log_channel_list
));
530 * We just set found instead of returning because
531 * the channel might be on the list more than once.
545 log_option(log_context lc
, int option
, int value
) {
551 case LOG_OPTION_DEBUG
:
555 lc
->flags
&= ~option
;
557 case LOG_OPTION_LEVEL
:
568 log_category_is_active(log_context lc
, int category
) {
573 if (category
>= 0 && category
< lc
->num_categories
&&
574 lc
->categories
[category
] != NULL
)
580 log_new_syslog_channel(unsigned int flags
, int level
, int facility
) {
583 chan
= memget(sizeof (struct log_channel
));
588 chan
->type
= log_syslog
;
591 chan
->out
.facility
= facility
;
592 chan
->references
= 0;
597 log_new_file_channel(unsigned int flags
, int level
,
598 const char *name
, FILE *stream
, unsigned int versions
,
599 unsigned long max_size
) {
602 chan
= memget(sizeof (struct log_channel
));
607 chan
->type
= log_file
;
615 * Quantize length to a multiple of 256. There's space for the
616 * NUL, since if len is a multiple of 256, the size chosen will
617 * be the next multiple.
619 chan
->out
.file
.name_size
= ((len
/ 256) + 1) * 256;
620 chan
->out
.file
.name
= memget(chan
->out
.file
.name_size
);
621 if (chan
->out
.file
.name
== NULL
) {
622 memput(chan
, sizeof (struct log_channel
));
627 strcpy(chan
->out
.file
.name
, name
);
629 chan
->out
.file
.name_size
= 0;
630 chan
->out
.file
.name
= NULL
;
632 chan
->out
.file
.stream
= stream
;
633 chan
->out
.file
.versions
= versions
;
634 chan
->out
.file
.max_size
= max_size
;
635 chan
->out
.file
.owner
= getuid();
636 chan
->out
.file
.group
= getgid();
637 chan
->references
= 0;
642 log_set_file_owner(log_channel chan
, uid_t owner
, gid_t group
) {
643 if (chan
->type
!= log_file
) {
647 chan
->out
.file
.owner
= owner
;
648 chan
->out
.file
.group
= group
;
653 log_new_null_channel() {
656 chan
= memget(sizeof (struct log_channel
));
661 chan
->type
= log_null
;
662 chan
->flags
= LOG_CHANNEL_OFF
;
663 chan
->level
= log_info
;
664 chan
->references
= 0;
669 log_inc_references(log_channel chan
) {
679 log_dec_references(log_channel chan
) {
680 if (chan
== NULL
|| chan
->references
<= 0) {
689 log_get_channel_type(log_channel chan
) {
690 REQUIRE(chan
!= NULL
);
696 log_free_channel(log_channel chan
) {
697 if (chan
== NULL
|| chan
->references
<= 0) {
702 if (chan
->references
== 0) {
703 if (chan
->type
== log_file
) {
704 if ((chan
->flags
& LOG_CLOSE_STREAM
) &&
705 chan
->out
.file
.stream
!= NULL
)
706 (void)fclose(chan
->out
.file
.stream
);
707 if (chan
->out
.file
.name
!= NULL
)
708 memput(chan
->out
.file
.name
,
709 chan
->out
.file
.name_size
);
711 memput(chan
, sizeof (struct log_channel
));