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.3.2.3 2004/03/17 01:54:23 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"
47 # define VSPRINTF(x) strlen(vsprintf/**/x)
49 # define VSPRINTF(x) ((size_t)vsprintf x)
52 #include "logging_p.h"
54 static const int syslog_priority
[] = { LOG_DEBUG
, LOG_INFO
, LOG_NOTICE
,
55 LOG_WARNING
, LOG_ERR
, LOG_CRIT
};
57 static const char *months
[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
58 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
60 static const char *level_text
[] = {
61 "info: ", "notice: ", "warning: ", "error: ", "critical: "
65 version_rename(log_channel chan
) {
67 char old_name
[PATH_MAX
+1];
68 char new_name
[PATH_MAX
+1];
70 ver
= chan
->out
.file
.versions
;
73 if (ver
> LOG_MAX_VERSIONS
)
74 ver
= LOG_MAX_VERSIONS
;
76 * Need to have room for '.nn' (XXX assumes LOG_MAX_VERSIONS < 100)
78 if (strlen(chan
->out
.file
.name
) > (size_t)(PATH_MAX
-3))
80 for (ver
--; ver
> 0; ver
--) {
81 sprintf(old_name
, "%s.%d", chan
->out
.file
.name
, ver
-1);
82 sprintf(new_name
, "%s.%d", chan
->out
.file
.name
, ver
);
83 (void)isc_movefile(old_name
, new_name
);
85 sprintf(new_name
, "%s.0", chan
->out
.file
.name
);
86 (void)isc_movefile(chan
->out
.file
.name
, new_name
);
90 log_open_stream(log_channel chan
) {
96 if (chan
== NULL
|| chan
->type
!= log_file
) {
102 * Don't open already open streams
104 if (chan
->out
.file
.stream
!= NULL
)
105 return (chan
->out
.file
.stream
);
107 if (stat(chan
->out
.file
.name
, &sb
) < 0) {
108 if (errno
!= ENOENT
) {
110 "log_open_stream: stat of %s failed: %s",
111 chan
->out
.file
.name
, strerror(errno
));
112 chan
->flags
|= LOG_CHANNEL_BROKEN
;
117 regular
= (sb
.st_mode
& S_IFREG
);
119 if (chan
->out
.file
.versions
) {
122 "log_open_stream: want versions but %s isn't a regular file",
123 chan
->out
.file
.name
);
124 chan
->flags
|= LOG_CHANNEL_BROKEN
;
130 flags
= O_WRONLY
|O_CREAT
|O_APPEND
;
132 if ((chan
->flags
& LOG_TRUNCATE
) != 0) {
134 (void)unlink(chan
->out
.file
.name
);
138 "log_open_stream: want truncation but %s isn't a regular file",
139 chan
->out
.file
.name
);
140 chan
->flags
|= LOG_CHANNEL_BROKEN
;
146 fd
= open(chan
->out
.file
.name
, flags
,
147 S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
|S_IROTH
|S_IWOTH
);
149 syslog(LOG_ERR
, "log_open_stream: open(%s) failed: %s",
150 chan
->out
.file
.name
, strerror(errno
));
151 chan
->flags
|= LOG_CHANNEL_BROKEN
;
154 stream
= fdopen(fd
, "a");
155 if (stream
== NULL
) {
156 syslog(LOG_ERR
, "log_open_stream: fdopen() failed");
157 chan
->flags
|= LOG_CHANNEL_BROKEN
;
160 (void) fchown(fd
, chan
->out
.file
.owner
, chan
->out
.file
.group
);
162 chan
->out
.file
.stream
= stream
;
167 log_close_stream(log_channel chan
) {
170 if (chan
== NULL
|| chan
->type
!= log_file
) {
174 stream
= chan
->out
.file
.stream
;
175 chan
->out
.file
.stream
= NULL
;
176 if (stream
!= NULL
&& fclose(stream
) == EOF
)
182 log_close_debug_channels(log_context lc
) {
183 log_channel_list lcl
;
186 for (i
= 0; i
< lc
->num_categories
; i
++)
187 for (lcl
= lc
->categories
[i
]; lcl
!= NULL
; lcl
= lcl
->next
)
188 if (lcl
->channel
->type
== log_file
&&
189 lcl
->channel
->out
.file
.stream
!= NULL
&&
190 lcl
->channel
->flags
& LOG_REQUIRE_DEBUG
)
191 (void)log_close_stream(lcl
->channel
);
195 log_get_stream(log_channel chan
) {
196 if (chan
== NULL
|| chan
->type
!= log_file
) {
200 return (chan
->out
.file
.stream
);
204 log_get_filename(log_channel chan
) {
205 if (chan
== NULL
|| chan
->type
!= log_file
) {
209 return (chan
->out
.file
.name
);
213 log_check_channel(log_context lc
, int level
, log_channel chan
) {
214 int debugging
, chan_level
;
218 debugging
= ((lc
->flags
& LOG_OPTION_DEBUG
) != 0);
221 * If not debugging, short circuit debugging messages very early.
223 if (level
> 0 && !debugging
)
226 if ((chan
->flags
& (LOG_CHANNEL_BROKEN
|LOG_CHANNEL_OFF
)) != 0)
229 /* Some channels only log when debugging is on. */
230 if ((chan
->flags
& LOG_REQUIRE_DEBUG
) && !debugging
)
233 /* Some channels use the global level. */
234 if ((chan
->flags
& LOG_USE_CONTEXT_LEVEL
) != 0) {
235 chan_level
= lc
->level
;
237 chan_level
= chan
->level
;
239 if (level
> chan_level
)
246 log_check(log_context lc
, int category
, int level
) {
247 log_channel_list lcl
;
252 debugging
= ((lc
->flags
& LOG_OPTION_DEBUG
) != 0);
255 * If not debugging, short circuit debugging messages very early.
257 if (level
> 0 && !debugging
)
260 if (category
< 0 || category
> lc
->num_categories
)
261 category
= 0; /* use default */
262 lcl
= lc
->categories
[category
];
265 lcl
= lc
->categories
[0];
268 for ( /* nothing */; lcl
!= NULL
; lcl
= lcl
->next
) {
269 if (log_check_channel(lc
, level
, lcl
->channel
))
276 log_vwrite(log_context lc
, int category
, int level
, const char *format
,
278 log_channel_list lcl
;
279 int pri
, debugging
, did_vsprintf
= 0;
280 int original_category
;
289 const char *category_name
;
290 const char *level_str
;
296 debugging
= (lc
->flags
& LOG_OPTION_DEBUG
);
299 * If not debugging, short circuit debugging messages very early.
301 if (level
> 0 && !debugging
)
304 if (category
< 0 || category
> lc
->num_categories
)
305 category
= 0; /* use default */
306 original_category
= category
;
307 lcl
= lc
->categories
[category
];
310 lcl
= lc
->categories
[0];
314 * Get the current time and format it.
317 if (gettimeofday(&tv
, NULL
) < 0) {
318 syslog(LOG_INFO
, "gettimeofday failed in log_vwrite()");
322 local_tm
= localtime_r(&tt
, &tm_tmp
);
324 local_tm
= localtime(&tt
);
326 if (local_tm
!= NULL
) {
327 sprintf(time_buf
, "%02d-%s-%4d %02d:%02d:%02d.%03ld ",
328 local_tm
->tm_mday
, months
[local_tm
->tm_mon
],
329 local_tm
->tm_year
+1900, local_tm
->tm_hour
,
330 local_tm
->tm_min
, local_tm
->tm_sec
,
331 (long)tv
.tv_usec
/1000);
336 * Make a string representation of the current category and level
339 if (lc
->category_names
!= NULL
&&
340 lc
->category_names
[original_category
] != NULL
)
341 category_name
= lc
->category_names
[original_category
];
345 if (level
>= log_critical
) {
347 sprintf(level_buf
, "debug %d: ", level
);
348 level_str
= level_buf
;
350 level_str
= level_text
[-level
-1];
352 sprintf(level_buf
, "level %d: ", level
);
353 level_str
= level_buf
;
357 * Write the message to channels.
359 for ( /* nothing */; lcl
!= NULL
; lcl
= lcl
->next
) {
362 if (!log_check_channel(lc
, level
, chan
))
366 if (VSPRINTF((lc
->buffer
, format
, args
)) >
367 (size_t)LOG_BUFFER_SIZE
) {
369 "memory overrun in log_vwrite()");
375 switch (chan
->type
) {
377 if (level
>= log_critical
)
378 pri
= (level
>= 0) ? 0 : -level
;
381 syslog(chan
->out
.facility
|syslog_priority
[pri
],
383 (chan
->flags
& LOG_TIMESTAMP
) ? time_buf
: "",
384 (chan
->flags
& LOG_PRINT_CATEGORY
) ?
386 (chan
->flags
& LOG_PRINT_LEVEL
) ?
391 stream
= chan
->out
.file
.stream
;
392 if (stream
== NULL
) {
393 stream
= log_open_stream(chan
);
397 if (chan
->out
.file
.max_size
!= ULONG_MAX
) {
403 chan
->out
.file
.max_size
) {
405 * try to roll over the log files,
406 * ignoring all all return codes
407 * except the open (we don't want
408 * to write any more anyway)
410 log_close_stream(chan
);
411 version_rename(chan
);
412 stream
= log_open_stream(chan
);
417 fprintf(stream
, "%s%s%s%s\n",
418 (chan
->flags
& LOG_TIMESTAMP
) ? time_buf
: "",
419 (chan
->flags
& LOG_PRINT_CATEGORY
) ?
421 (chan
->flags
& LOG_PRINT_LEVEL
) ?
430 "unknown channel type in log_vwrite()");
436 log_write(log_context lc
, int category
, int level
, const char *format
, ...) {
439 va_start(args
, format
);
440 log_vwrite(lc
, category
, level
, format
, args
);
445 * Functions to create, set, or destroy contexts
449 log_new_context(int num_categories
, char **category_names
, log_context
*lc
) {
452 nlc
= memget(sizeof (struct log_context
));
457 nlc
->num_categories
= num_categories
;
458 nlc
->category_names
= category_names
;
459 nlc
->categories
= memget(num_categories
* sizeof (log_channel_list
));
460 if (nlc
->categories
== NULL
) {
461 memput(nlc
, sizeof (struct log_context
));
465 memset(nlc
->categories
, '\0',
466 num_categories
* sizeof (log_channel_list
));
474 log_free_context(log_context lc
) {
475 log_channel_list lcl
, lcl_next
;
481 for (i
= 0; i
< lc
->num_categories
; i
++)
482 for (lcl
= lc
->categories
[i
]; lcl
!= NULL
; lcl
= lcl_next
) {
483 lcl_next
= lcl
->next
;
485 (void)log_free_channel(chan
);
486 memput(lcl
, sizeof (struct log_channel_list
));
488 memput(lc
->categories
,
489 lc
->num_categories
* sizeof (log_channel_list
));
490 memput(lc
, sizeof (struct log_context
));
494 log_add_channel(log_context lc
, int category
, log_channel chan
) {
495 log_channel_list lcl
;
497 if (lc
== NULL
|| category
< 0 || category
>= lc
->num_categories
) {
502 lcl
= memget(sizeof (struct log_channel_list
));
508 lcl
->next
= lc
->categories
[category
];
509 lc
->categories
[category
] = lcl
;
515 log_remove_channel(log_context lc
, int category
, log_channel chan
) {
516 log_channel_list lcl
, prev_lcl
, next_lcl
;
519 if (lc
== NULL
|| category
< 0 || category
>= lc
->num_categories
) {
524 for (prev_lcl
= NULL
, lcl
= lc
->categories
[category
];
527 next_lcl
= lcl
->next
;
528 if (lcl
->channel
== chan
) {
529 log_free_channel(chan
);
530 if (prev_lcl
!= NULL
)
531 prev_lcl
->next
= next_lcl
;
533 lc
->categories
[category
] = next_lcl
;
534 memput(lcl
, sizeof (struct log_channel_list
));
536 * We just set found instead of returning because
537 * the channel might be on the list more than once.
551 log_option(log_context lc
, int option
, int value
) {
557 case LOG_OPTION_DEBUG
:
561 lc
->flags
&= ~option
;
563 case LOG_OPTION_LEVEL
:
574 log_category_is_active(log_context lc
, int category
) {
579 if (category
>= 0 && category
< lc
->num_categories
&&
580 lc
->categories
[category
] != NULL
)
586 log_new_syslog_channel(unsigned int flags
, int level
, int facility
) {
589 chan
= memget(sizeof (struct log_channel
));
594 chan
->type
= log_syslog
;
597 chan
->out
.facility
= facility
;
598 chan
->references
= 0;
603 log_new_file_channel(unsigned int flags
, int level
,
604 const char *name
, FILE *stream
, unsigned int versions
,
605 unsigned long max_size
) {
608 chan
= memget(sizeof (struct log_channel
));
613 chan
->type
= log_file
;
621 * Quantize length to a multiple of 256. There's space for the
622 * NUL, since if len is a multiple of 256, the size chosen will
623 * be the next multiple.
625 chan
->out
.file
.name_size
= ((len
/ 256) + 1) * 256;
626 chan
->out
.file
.name
= memget(chan
->out
.file
.name_size
);
627 if (chan
->out
.file
.name
== NULL
) {
628 memput(chan
, sizeof (struct log_channel
));
633 strcpy(chan
->out
.file
.name
, name
);
635 chan
->out
.file
.name_size
= 0;
636 chan
->out
.file
.name
= NULL
;
638 chan
->out
.file
.stream
= stream
;
639 chan
->out
.file
.versions
= versions
;
640 chan
->out
.file
.max_size
= max_size
;
641 chan
->out
.file
.owner
= getuid();
642 chan
->out
.file
.group
= getgid();
643 chan
->references
= 0;
648 log_set_file_owner(log_channel chan
, uid_t owner
, gid_t group
) {
649 if (chan
->type
!= log_file
) {
653 chan
->out
.file
.owner
= owner
;
654 chan
->out
.file
.group
= group
;
659 log_new_null_channel() {
662 chan
= memget(sizeof (struct log_channel
));
667 chan
->type
= log_null
;
668 chan
->flags
= LOG_CHANNEL_OFF
;
669 chan
->level
= log_info
;
670 chan
->references
= 0;
675 log_inc_references(log_channel chan
) {
685 log_dec_references(log_channel chan
) {
686 if (chan
== NULL
|| chan
->references
<= 0) {
695 log_get_channel_type(log_channel chan
) {
696 REQUIRE(chan
!= NULL
);
702 log_free_channel(log_channel chan
) {
703 if (chan
== NULL
|| chan
->references
<= 0) {
708 if (chan
->references
== 0) {
709 if (chan
->type
== log_file
) {
710 if ((chan
->flags
& LOG_CLOSE_STREAM
) &&
711 chan
->out
.file
.stream
!= NULL
)
712 (void)fclose(chan
->out
.file
.stream
);
713 if (chan
->out
.file
.name
!= NULL
)
714 memput(chan
->out
.file
.name
,
715 chan
->out
.file
.name_size
);
717 memput(chan
, sizeof (struct log_channel
));