log.c (10473B)
1 // 2 // Log module 3 // 4 // How Linux printk() handles recursion, buffering, etc: 5 // https://lwn.net/Articles/780556/ 6 // 7 8 #include <assert.h> 9 #include <errno.h> 10 #include <inttypes.h> 11 #include <stdarg.h> 12 #include <stdbool.h> 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <time.h> 17 #include <uv.h> 18 19 #include "auto/config.h" 20 #include "nvim/ascii_defs.h" 21 #include "nvim/eval/vars.h" 22 #include "nvim/globals.h" 23 #include "nvim/log.h" 24 #include "nvim/memory.h" 25 #include "nvim/message.h" 26 #include "nvim/os/fs.h" 27 #include "nvim/os/os.h" 28 #include "nvim/os/os_defs.h" 29 #include "nvim/os/stdpaths_defs.h" 30 #include "nvim/os/time.h" 31 #include "nvim/path.h" 32 #include "nvim/ui_client.h" 33 34 /// Cached location of the expanded log file path decided by log_path_init(). 35 static char log_file_path[MAXPATHL + 1] = { 0 }; 36 37 static bool did_log_init = false; 38 static uv_mutex_t mutex; 39 40 #include "log.c.generated.h" 41 42 #ifdef HAVE_EXECINFO_BACKTRACE 43 # include <execinfo.h> 44 #endif 45 46 static bool log_try_create(char *fname) 47 { 48 if (fname == NULL || fname[0] == NUL) { 49 return false; 50 } 51 FILE *log_file = fopen(fname, "a"); 52 if (log_file == NULL) { 53 return false; 54 } 55 fclose(log_file); 56 return true; 57 } 58 59 /// Initializes the log file path and sets $NVIM_LOG_FILE if empty. 60 /// 61 /// Tries $NVIM_LOG_FILE, or falls back to $XDG_STATE_HOME/nvim/nvim.log. Failed 62 /// initialization indicates either a bug in expand_env() or both $NVIM_LOG_FILE 63 /// and $HOME environment variables are undefined. 64 static void log_path_init(void) 65 { 66 size_t size = sizeof(log_file_path); 67 expand_env("$" ENV_LOGFILE, log_file_path, (int)size - 1); 68 if (strequal("$" ENV_LOGFILE, log_file_path) 69 || log_file_path[0] == NUL 70 || os_isdir(log_file_path) 71 || !log_try_create(log_file_path)) { 72 // Make $XDG_STATE_HOME if it does not exist. 73 char *loghome = get_xdg_home(kXDGStateHome); 74 char *failed_dir = NULL; 75 bool log_dir_failure = false; 76 if (!os_isdir(loghome)) { 77 log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir, NULL) != 0); 78 } 79 XFREE_CLEAR(loghome); 80 // Invalid $NVIM_LOG_FILE or failed to expand; fall back to default. 81 char *defaultpath = stdpaths_user_state_subpath("nvim.log", 0, true); 82 size_t len = xstrlcpy(log_file_path, defaultpath, size); 83 xfree(defaultpath); 84 // Fall back to $CWD/nvim.log 85 if (len >= size || !log_try_create(log_file_path)) { 86 len = xstrlcpy(log_file_path, "nvim.log", size); 87 } 88 // Fall back to stderr 89 if (len >= size || !log_try_create(log_file_path)) { 90 log_file_path[0] = NUL; 91 return; 92 } 93 os_setenv(ENV_LOGFILE, log_file_path, true); 94 if (log_dir_failure) { 95 WLOG("Failed to create directory %s for writing logs: %s", 96 failed_dir, os_strerror(log_dir_failure)); 97 } 98 XFREE_CLEAR(failed_dir); 99 } 100 } 101 102 void log_init(void) 103 { 104 uv_mutex_init_recursive(&mutex); 105 // AFTER init_homedir ("~", XDG) and set_init_1 (env vars). 22b52dd462e5 #11501 106 log_path_init(); 107 did_log_init = true; 108 } 109 110 void log_lock(void) 111 { 112 uv_mutex_lock(&mutex); 113 } 114 115 void log_unlock(void) 116 { 117 uv_mutex_unlock(&mutex); 118 } 119 120 /// Logs a message to $NVIM_LOG_FILE. 121 /// 122 /// @param log_level Log level (see log.h) 123 /// @param context Description of a shared context or subsystem 124 /// @param func_name Function name, or NULL 125 /// @param line_num Source line number, or -1 126 /// @param eol Append linefeed "\n" 127 /// @param fmt printf-style format string 128 /// 129 /// @return true if log was emitted normally, false if failed or recursive 130 bool logmsg(int log_level, const char *context, const char *func_name, int line_num, bool eol, 131 const char *fmt, ...) 132 FUNC_ATTR_PRINTF(6, 7) 133 { 134 static bool recursive = false; 135 static bool did_msg = false; // Showed recursion message? 136 if (!did_log_init) { 137 g_stats.log_skip++; 138 // set_init_1 may try logging before we are ready. 6f27f5ef91b3 #10183 139 return false; 140 } 141 142 #ifndef NVIM_LOG_DEBUG 143 // This should rarely happen (callsites are compiled out), but to be sure. 144 // TODO(bfredl): allow log levels to be configured at runtime 145 if (log_level < LOGLVL_WRN) { 146 return false; 147 } 148 #endif 149 150 #ifdef EXITFREE 151 // Logging after we've already started freeing all our memory will only cause 152 // pain. We need access to VV_PROGPATH, homedir, etc. 153 if (entered_free_all_mem) { 154 fprintf(stderr, "FATAL: error in free_all_mem\n %s %s %d: ", context, func_name, line_num); 155 va_list args; 156 va_start(args, fmt); 157 vfprintf(stderr, fmt, args); 158 va_end(args); 159 if (eol) { 160 fprintf(stderr, "\n"); 161 } 162 abort(); 163 } 164 #endif 165 166 log_lock(); 167 if (recursive) { 168 if (!did_msg) { 169 did_msg = true; 170 msg_schedule_semsg("E5430: %s:%d: recursive log!", func_name ? func_name : context, line_num); 171 } 172 g_stats.log_skip++; 173 log_unlock(); 174 return false; 175 } 176 recursive = true; 177 bool ret = false; 178 FILE *log_file = open_log_file(); 179 180 va_list args; 181 va_start(args, fmt); 182 ret = v_do_log_to_file(log_file, log_level, context, func_name, line_num, 183 eol, fmt, args); 184 va_end(args); 185 186 if (log_file != stderr && log_file != stdout) { 187 fclose(log_file); 188 } 189 190 recursive = false; 191 log_unlock(); 192 return ret; 193 } 194 195 void log_uv_handles(void *loop) 196 { 197 uv_loop_t *l = loop; 198 log_lock(); 199 FILE *log_file = open_log_file(); 200 201 uv_print_all_handles(l, log_file); 202 203 if (log_file != stderr && log_file != stdout) { 204 fclose(log_file); 205 } 206 207 log_unlock(); 208 } 209 210 /// Open the log file for appending. 211 /// 212 /// @return Log file, or stderr on failure 213 FILE *open_log_file(void) 214 { 215 errno = 0; 216 if (log_file_path[0]) { 217 FILE *f = fopen(log_file_path, "a"); 218 if (f != NULL) { 219 return f; 220 } 221 } 222 223 // May happen if: 224 // - fopen() failed 225 // - LOG() is called before log_init() 226 // - Directory does not exist 227 // - File is not writable 228 do_log_to_file(stderr, LOGLVL_ERR, NULL, __func__, __LINE__, true, 229 "failed to open $" ENV_LOGFILE " (%s): %s", 230 strerror(errno), log_file_path); 231 return stderr; 232 } 233 234 #ifdef HAVE_EXECINFO_BACKTRACE 235 void log_callstack_to_file(FILE *log_file, const char *const func_name, const int line_num) 236 { 237 void *trace[100]; 238 int trace_size = backtrace(trace, ARRAY_SIZE(trace)); 239 240 char exepath[MAXPATHL] = { 0 }; 241 size_t exepathlen = MAXPATHL; 242 if (os_exepath(exepath, &exepathlen) != 0) { 243 abort(); 244 } 245 assert(24 + exepathlen < IOSIZE); // Must fit in `cmdbuf` below. 246 247 char cmdbuf[IOSIZE + (20 * ARRAY_SIZE(trace)) + MAXPATHL]; 248 snprintf(cmdbuf, sizeof(cmdbuf), "addr2line -e %s -f -p", exepath); 249 for (int i = 1; i < trace_size; i++) { 250 char buf[20]; // 64-bit pointer 0xNNNNNNNNNNNNNNNN with leading space. 251 snprintf(buf, sizeof(buf), " %p", trace[i]); 252 xstrlcat(cmdbuf, buf, sizeof(cmdbuf)); 253 } 254 // Now we have a command string like: 255 // addr2line -e /path/to/exe -f -p 0x123 0x456 ... 256 257 do_log_to_file(log_file, LOGLVL_DBG, NULL, func_name, line_num, true, "trace:"); 258 FILE *fp = popen(cmdbuf, "r"); 259 assert(fp); 260 char linebuf[IOSIZE]; 261 while (fgets(linebuf, sizeof(linebuf) - 1, fp) != NULL) { 262 fprintf(log_file, " %s", linebuf); 263 } 264 pclose(fp); 265 266 if (log_file != stderr && log_file != stdout) { 267 fclose(log_file); 268 } 269 } 270 271 void log_callstack(const char *const func_name, const int line_num) 272 { 273 log_lock(); 274 FILE *log_file = open_log_file(); 275 log_callstack_to_file(log_file, func_name, line_num); 276 log_unlock(); 277 } 278 #endif 279 280 static bool do_log_to_file(FILE *log_file, int log_level, const char *context, 281 const char *func_name, int line_num, bool eol, const char *fmt, ...) 282 FUNC_ATTR_PRINTF(7, 8) 283 { 284 va_list args; 285 va_start(args, fmt); 286 bool ret = v_do_log_to_file(log_file, log_level, context, func_name, 287 line_num, eol, fmt, args); 288 va_end(args); 289 290 return ret; 291 } 292 293 static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context, 294 const char *func_name, int line_num, bool eol, const char *fmt, 295 va_list args) 296 FUNC_ATTR_PRINTF(7, 0) 297 { 298 // Name of the Nvim instance that produced the log. 299 static char name[32] = { 0 }; 300 301 static const char *log_levels[] = { 302 [LOGLVL_DBG] = "DBG", 303 [LOGLVL_INF] = "INF", 304 [LOGLVL_WRN] = "WRN", 305 [LOGLVL_ERR] = "ERR", 306 }; 307 assert(log_level >= LOGLVL_DBG && log_level <= LOGLVL_ERR); 308 309 // Format the timestamp. 310 struct tm local_time; 311 if (os_localtime(&local_time) == NULL) { 312 return false; 313 } 314 char date_time[20]; 315 if (strftime(date_time, sizeof(date_time), "%Y-%m-%dT%H:%M:%S", &local_time) == 0) { 316 return false; 317 } 318 319 int millis = 0; 320 uv_timeval64_t curtime; 321 if (uv_gettimeofday(&curtime) == 0) { 322 millis = (int)curtime.tv_usec / 1000; 323 } 324 325 bool ui = !!ui_client_channel_id; // Running as a UI client (--remote-ui). 326 327 // Regenerate the name when: 328 // - UI client (to ensure "ui" is in the name) 329 // - not set yet 330 // - no v:servername yet 331 bool regen = ui || name[0] == NUL || name[0] == '?'; 332 333 // Get a name for this Nvim instance. 334 // TODO(justinmk): expose this as v:name ? 335 if (regen) { 336 char parent_buf[MAXPATHL]; 337 // Parent servername ($NVIM). 338 const char *parent = path_tail(os_getenv_buf(ENV_NVIM, parent_buf, sizeof(parent_buf))); 339 // Servername. Empty until starting=false. 340 const char *serv = path_tail(get_vim_var_str(VV_SEND_SERVER)); 341 if (parent[0] != NUL) { 342 snprintf(name, sizeof(name), ui ? "ui/c/%s" : "c/%s", parent); // "c/" = child of $NVIM. 343 } else if (serv[0] != NUL) { 344 snprintf(name, sizeof(name), ui ? "ui/%s" : "%s", serv); 345 } else { 346 int64_t pid = os_get_pid(); 347 snprintf(name, sizeof(name), "%s.%-5" PRId64, ui ? "ui" : "?", pid); 348 } 349 } 350 351 // Print the log message. 352 int rv = (line_num == -1 || func_name == NULL) 353 ? fprintf(log_file, "%s %s.%03d %-10s %s", 354 log_levels[log_level], date_time, millis, name, 355 (context == NULL ? "?:" : context)) 356 : fprintf(log_file, "%s %s.%03d %-10s %s%s:%d: ", 357 log_levels[log_level], date_time, millis, name, 358 (context == NULL ? "" : context), 359 func_name, line_num); 360 361 if (rv < 0) { 362 return false; 363 } 364 if (vfprintf(log_file, fmt, args) < 0) { 365 return false; 366 } 367 if (eol) { 368 fputc('\n', log_file); 369 } 370 if (fflush(log_file) == EOF) { 371 return false; 372 } 373 374 return true; 375 }