1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2/* 3 * Copyright 2014-2020 Couchbase, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18#include "settings.h" 19#include "logging.h" 20#include "internal.h" /* for lcb_getenv* */ 21#include <stdio.h> 22#include <stdarg.h> 23 24#ifdef _WIN32 25#define flockfile(x) (void)0 26#define funlockfile(x) (void)0 27#endif 28 29#if defined(unix) || defined(__unix__) || defined(__unix) || defined(_POSIX_VERSION) 30#include <unistd.h> 31#include <pthread.h> 32#include <sys/types.h> 33 34/** XXX: If any of these blocks give problems for your platform, just 35 * erase it and have it use the fallback implementation. This isn't core 36 * functionality of the library, but is a rather helpful feature in order 37 * to get the thread/process identifier 38 */ 39 40#if defined(__linux__) 41#include <sys/syscall.h> 42#define GET_THREAD_ID() (long)syscall(SYS_gettid) 43#define THREAD_ID_FMT "ld" 44#elif defined(__APPLE__) 45#define GET_THREAD_ID() getpid(), pthread_mach_thread_np(pthread_self()) 46#define THREAD_ID_FMT "d/%x" 47#elif defined(__sun) && defined(__SVR4) 48#include <thread.h> 49/* Thread IDs are not global in solaris, so it's nice to print the PID alongside it */ 50#define GET_THREAD_ID() getpid(), thr_self() 51#define THREAD_ID_FMT "ld/%u" 52#elif defined(__FreeBSD__) 53/* Like solaris, but thr_self is a bit different here */ 54#include <sys/thr.h> 55static long ret_thr_self(void) 56{ 57 long tmp; 58 thr_self(&tmp); 59 return tmp; 60} 61#define GET_THREAD_ID() getpid(), ret_thr_self() 62#define THREAD_ID_FMT "d/%ld" 63#else 64/* other unix? */ 65#define GET_THREAD_ID() 0 66#define THREAD_ID_FMT "d" 67#endif 68#elif defined(_WIN32) 69#define GET_THREAD_ID() GetCurrentThreadId() 70#define THREAD_ID_FMT "d" 71#else 72#define GET_THREAD_ID() 0 73#define THREAD_ID_FMT "d" 74#endif 75 76static hrtime_t start_time = 0; 77 78static void console_log(const lcb_LOGGER *procs, uint64_t iid, const char *subsys, lcb_LOG_SEVERITY severity, 79 const char *srcfile, int srcline, const char *fmt, va_list ap); 80 81static struct lcb_CONSOLELOGGER console_logprocs = {{console_log, NULL}, NULL, LCB_LOG_INFO /* Minimum severity */}; 82 83lcb_LOGGER *lcb_console_logger = &console_logprocs.base; 84 85/** 86 * Return a string representation of the severity level 87 */ 88static const char *level_to_string(int severity) 89{ 90 switch (severity) { 91 case LCB_LOG_TRACE: 92 return "TRACE"; 93 case LCB_LOG_DEBUG: 94 return "DEBUG"; 95 case LCB_LOG_INFO: 96 return "INFO"; 97 case LCB_LOG_WARN: 98 return "WARN"; 99 case LCB_LOG_ERROR: 100 return "ERROR"; 101 case LCB_LOG_FATAL: 102 return "FATAL"; 103 default: 104 return ""; 105 } 106} 107 108/** 109 * Default logging callback for the verbose logger. 110 */ 111static void console_log(const lcb_LOGGER *procs, uint64_t iid, const char *subsys, lcb_LOG_SEVERITY severity, 112 const char *srcfile, int srcline, const char *fmt, va_list ap) 113{ 114 FILE *fp; 115 hrtime_t now; 116 struct lcb_CONSOLELOGGER *vprocs = (struct lcb_CONSOLELOGGER *)procs; 117 118 if ((int)severity < vprocs->minlevel) { 119 return; 120 } 121 122 if (!start_time) { 123 start_time = gethrtime(); 124 } 125 126 now = gethrtime(); 127 if (now == start_time) { 128 now++; 129 } 130 131 fp = vprocs->fp ? vprocs->fp : stderr; 132 133 flockfile(fp); 134 fprintf(fp, "%lums ", (unsigned long)(now - start_time) / 1000000); 135 136 fprintf(fp, "[I%" PRIx64 "] {%" THREAD_ID_FMT "} [%s] (%s - L:%d) ", iid, GET_THREAD_ID(), 137 level_to_string(severity), subsys, srcline); 138 vfprintf(fp, fmt, ap); 139 fprintf(fp, "\n"); 140 funlockfile(fp); 141 142 (void)procs; 143 (void)srcfile; 144} 145 146LCB_INTERNAL_API 147void lcb_log(const struct lcb_settings_st *settings, const char *subsys, int severity, const char *srcfile, int srcline, 148 const char *fmt, ...) 149{ 150 va_list ap; 151 lcb_LOGGER_CALLBACK callback; 152 153 if (!settings->logger) { 154 return; 155 } 156 157 callback = settings->logger->callback; 158 159 if (!callback) 160 return; 161 162 va_start(ap, fmt); 163 callback(settings->logger, settings->iid, subsys, severity, srcfile, srcline, fmt, ap); 164 va_end(ap); 165} 166 167LCB_INTERNAL_API 168void lcb_log_badconfig(const struct lcb_settings_st *settings, const char *subsys, int severity, const char *srcfile, 169 int srcline, const lcbvb_CONFIG *vbc, const char *origin_txt) 170{ 171 const char *errstr = lcbvb_get_error(vbc); 172 if (!errstr) { 173 errstr = "<FIXME: No error string provided for parse failure>"; 174 } 175 176 lcb_log(settings, subsys, severity, srcfile, srcline, "vBucket config parsing failed: %s. Raw text in DEBUG level", 177 errstr); 178 if (!origin_txt) { 179 origin_txt = "<FIXME: No origin text available>"; 180 } 181 lcb_log(settings, subsys, LCB_LOG_DEBUG, srcfile, srcline, "%s", origin_txt); 182} 183 184lcb_LOGGER *lcb_init_console_logger(void) 185{ 186 char vbuf[1024]; 187 char namebuf[PATH_MAX] = {0}; 188 int lvl = 0; 189 int has_file = 0; 190 191 has_file = lcb_getenv_nonempty("LCB_LOGFILE", namebuf, sizeof(namebuf)); 192 if (has_file && console_logprocs.fp == NULL) { 193 FILE *fp = fopen(namebuf, "a"); 194 if (!fp) { 195 fprintf(stderr, "libcouchbase: could not open file '%s' for logging output. (%s)\n", namebuf, 196 strerror(errno)); 197 } 198 console_logprocs.fp = fp; 199 } 200 201 if (!lcb_getenv_nonempty("LCB_LOGLEVEL", vbuf, sizeof(vbuf))) { 202 return NULL; 203 } 204 205 if (sscanf(vbuf, "%d", &lvl) != 1) { 206 return NULL; 207 } 208 209 if (!lvl) { 210 /** "0" */ 211 return NULL; 212 } 213 214 /** The "lowest" level we can expose is WARN, e.g. ERROR-1 */ 215 lvl = LCB_LOG_ERROR - lvl; 216 console_logprocs.minlevel = lvl; 217 return lcb_console_logger; 218} 219 220LIBCOUCHBASE_API lcb_STATUS lcb_logger_create(lcb_LOGGER **logger, void *cookie) 221{ 222 *logger = (lcb_LOGGER *)calloc(1, sizeof(lcb_LOGGER)); 223 (*logger)->cookie = cookie; 224 return LCB_SUCCESS; 225} 226 227LIBCOUCHBASE_API lcb_STATUS lcb_logger_destroy(lcb_LOGGER *logger) 228{ 229 free(logger); 230 return LCB_SUCCESS; 231} 232 233LIBCOUCHBASE_API lcb_STATUS lcb_logger_callback(lcb_LOGGER *logger, lcb_LOGGER_CALLBACK callback) 234{ 235 logger->callback = callback; 236 return LCB_SUCCESS; 237} 238 239LIBCOUCHBASE_API lcb_STATUS lcb_logger_cookie(const lcb_LOGGER *logger, void **cookie) 240{ 241 *cookie = logger->cookie; 242 return LCB_SUCCESS; 243} 244