You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							242 lines
						
					
					
						
							6.1 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							242 lines
						
					
					
						
							6.1 KiB
						
					
					
				| const debug = require('debug')('log4js:fileSync'); | |
| const path = require('path'); | |
| const fs = require('fs'); | |
| const os = require('os'); | |
| 
 | |
| const eol = os.EOL || '\n'; | |
| 
 | |
| function touchFile(file, options) { | |
|   // if the file exists, nothing to do | |
|   if (fs.existsSync(file)) { | |
|     return; | |
|   } | |
| 
 | |
|   // attempt to create the directory | |
|   const mkdir = (dir) => { | |
|     try { | |
|       return fs.mkdirSync(dir, {recursive: true}); | |
|     } | |
|     // backward-compatible fs.mkdirSync for nodejs pre-10.12.0 (without recursive option) | |
|     catch (e) { | |
|       // recursive creation of parent first | |
|       if (e.code === 'ENOENT') { | |
|         mkdir(path.dirname(dir)); | |
|         return mkdir(dir); | |
|       } | |
| 
 | |
|       // throw error for all except EEXIST and EROFS (read-only filesystem) | |
|       if (e.code !== 'EEXIST' && e.code !== 'EROFS') { | |
|         throw e; | |
|       } | |
| 
 | |
|       // EEXIST: throw if file and not directory | |
|       // EROFS : throw if directory not found | |
|       else { | |
|         try { | |
|           if (fs.statSync(dir).isDirectory()) { | |
|             return dir; | |
|           } | |
|           throw e; | |
|         } catch (err) { | |
|           throw e; | |
|         } | |
|       } | |
|     } | |
|   }; | |
|   mkdir(path.dirname(file)); | |
| 
 | |
|   // touch the file to apply flags (like w to truncate the file) | |
|   const id = fs.openSync(file, options.flags, options.mode); | |
|   fs.closeSync(id); | |
| } | |
| 
 | |
| class RollingFileSync { | |
|   constructor(filename, size, backups, options) { | |
|     debug('In RollingFileStream'); | |
| 
 | |
|     function throwErrorIfArgumentsAreNotValid() { | |
|       if (!filename || !size || size <= 0) { | |
|         throw new Error('You must specify a filename and file size'); | |
|       } | |
|     } | |
| 
 | |
|     throwErrorIfArgumentsAreNotValid(); | |
| 
 | |
|     this.filename = filename; | |
|     this.size = size; | |
|     this.backups = backups; | |
|     this.options = options; | |
|     this.currentSize = 0; | |
| 
 | |
|     function currentFileSize(file) { | |
|       let fileSize = 0; | |
| 
 | |
|       try { | |
|         fileSize = fs.statSync(file).size; | |
|       } catch (e) { | |
|         // file does not exist | |
|         touchFile(file, options); | |
|       } | |
|       return fileSize; | |
|     } | |
| 
 | |
|     this.currentSize = currentFileSize(this.filename); | |
|   } | |
| 
 | |
|   shouldRoll() { | |
|     debug('should roll with current size %d, and max size %d', this.currentSize, this.size); | |
|     return this.currentSize >= this.size; | |
|   } | |
| 
 | |
|   roll(filename) { | |
|     const that = this; | |
|     const nameMatcher = new RegExp(`^${path.basename(filename)}`); | |
| 
 | |
|     function justTheseFiles(item) { | |
|       return nameMatcher.test(item); | |
|     } | |
| 
 | |
|     function index(filename_) { | |
|       return parseInt(filename_.substring((`${path.basename(filename)}.`).length), 10) || 0; | |
|     } | |
| 
 | |
|     function byIndex(a, b) { | |
|       if (index(a) > index(b)) { | |
|         return 1; | |
|       } | |
|       if (index(a) < index(b)) { | |
|         return -1; | |
|       } | |
| 
 | |
|       return 0; | |
|     } | |
| 
 | |
|     function increaseFileIndex(fileToRename) { | |
|       const idx = index(fileToRename); | |
|       debug(`Index of ${fileToRename} is ${idx}`); | |
|       if (that.backups === 0) { | |
|         fs.truncateSync(filename, 0); | |
|       } else if (idx < that.backups) { | |
|         // on windows, you can get a EEXIST error if you rename a file to an existing file | |
|         // so, we'll try to delete the file we're renaming to first | |
|         try { | |
|           fs.unlinkSync(`${filename}.${idx + 1}`); | |
|         } catch (e) { | |
|           // ignore err: if we could not delete, it's most likely that it doesn't exist | |
|         } | |
| 
 | |
|         debug(`Renaming ${fileToRename} -> ${filename}.${idx + 1}`); | |
|         fs.renameSync(path.join(path.dirname(filename), fileToRename), `${filename}.${idx + 1}`); | |
|       } | |
|     } | |
| 
 | |
|     function renameTheFiles() { | |
|       // roll the backups (rename file.n to file.n+1, where n <= numBackups) | |
|       debug('Renaming the old files'); | |
| 
 | |
|       const files = fs.readdirSync(path.dirname(filename)); | |
|       files.filter(justTheseFiles).sort(byIndex).reverse().forEach(increaseFileIndex); | |
|     } | |
| 
 | |
|     debug('Rolling, rolling, rolling'); | |
|     renameTheFiles(); | |
|   } | |
| 
 | |
|   /* eslint no-unused-vars:0 */ | |
|   write(chunk, encoding) { | |
|     const that = this; | |
| 
 | |
| 
 | |
|     function writeTheChunk() { | |
|       debug('writing the chunk to the file'); | |
|       that.currentSize += chunk.length; | |
|       fs.appendFileSync(that.filename, chunk); | |
|     } | |
| 
 | |
|     debug('in write'); | |
| 
 | |
| 
 | |
|     if (this.shouldRoll()) { | |
|       this.currentSize = 0; | |
|       this.roll(this.filename); | |
|     } | |
| 
 | |
|     writeTheChunk(); | |
|   } | |
| } | |
| 
 | |
| /** | |
|  * File Appender writing the logs to a text file. Supports rolling of logs by size. | |
|  * | |
|  * @param file file log messages will be written to | |
|  * @param layout a function that takes a logevent and returns a string | |
|  *   (defaults to basicLayout). | |
|  * @param logSize - the maximum size (in bytes) for a log file, | |
|  *   if not provided then logs won't be rotated. | |
|  * @param numBackups - the number of log files to keep after logSize | |
|  *   has been reached (default 5) | |
|  * @param timezoneOffset - optional timezone offset in minutes | |
|  *   (default system local) | |
|  * @param options - passed as is to fs options | |
|  */ | |
| function fileAppender(file, layout, logSize, numBackups, timezoneOffset, options) { | |
|   debug('fileSync appender created'); | |
|   file = path.normalize(file); | |
|   numBackups = (!numBackups && numBackups !== 0) ? 5 : numBackups; | |
| 
 | |
|   function openTheStream(filePath, fileSize, numFiles) { | |
|     let stream; | |
| 
 | |
|     if (fileSize) { | |
|       stream = new RollingFileSync( | |
|         filePath, | |
|         fileSize, | |
|         numFiles, | |
|         options | |
|       ); | |
|     } else { | |
|       stream = (((f) => { | |
|         // touch the file to apply flags (like w to truncate the file) | |
|         touchFile(f, options); | |
| 
 | |
|         return { | |
|           write(data) { | |
|             fs.appendFileSync(f, data); | |
|           } | |
|         }; | |
|       }))(filePath); | |
|     } | |
| 
 | |
|     return stream; | |
|   } | |
| 
 | |
|   const logFile = openTheStream(file, logSize, numBackups); | |
| 
 | |
|   return (loggingEvent) => { | |
|     logFile.write(layout(loggingEvent, timezoneOffset) + eol); | |
|   }; | |
| } | |
| 
 | |
| function configure(config, layouts) { | |
|   let layout = layouts.basicLayout; | |
|   if (config.layout) { | |
|     layout = layouts.layout(config.layout.type, config.layout); | |
|   } | |
| 
 | |
|   const options = { | |
|     flags: config.flags || 'a', | |
|     encoding: config.encoding || 'utf8', | |
|     mode: config.mode || 0o600 | |
|   }; | |
| 
 | |
|   return fileAppender( | |
|     config.filename, | |
|     layout, | |
|     config.maxLogSize, | |
|     config.backups, | |
|     config.timezoneOffset, | |
|     options | |
|   ); | |
| } | |
| 
 | |
| module.exports.configure = configure;
 |