github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/xlog/rotate_log_test.go (about) 1 package xlog 2 3 import ( 4 "archive/zip" 5 "context" 6 "errors" 7 "io" 8 "os" 9 "path/filepath" 10 "strconv" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/google/safeopen" 16 "github.com/stretchr/testify/require" 17 18 "github.com/benz9527/xboot/lib/id" 19 ) 20 21 func TestParseFileSizeUnit(t *testing.T) { 22 testcases := []struct { 23 size string 24 expected uint64 25 expectedErr bool 26 }{ 27 { 28 "abcMB", 29 0, 30 true, 31 }, 32 { 33 "_GB", 34 0, 35 true, 36 }, 37 { 38 "TB", 39 0, 40 true, 41 }, 42 { 43 "Y", 44 0, 45 true, 46 }, 47 { 48 "100B", 49 100 * uint64(B), 50 false, 51 }, 52 { 53 "100KB", 54 100 * uint64(KB), 55 false, 56 }, 57 { 58 "100MB", 59 100 * uint64(MB), 60 false, 61 }, 62 { 63 "100b", 64 100 * uint64(B), 65 false, 66 }, 67 { 68 "100kb", 69 100 * uint64(KB), 70 false, 71 }, 72 { 73 "100mb", 74 100 * uint64(MB), 75 false, 76 }, 77 { 78 "100kB", 79 100 * uint64(KB), 80 false, 81 }, 82 { 83 "100Mb", 84 100 * uint64(MB), 85 false, 86 }, 87 { 88 "100Kb", 89 100 * uint64(KB), 90 false, 91 }, 92 { 93 "100mB", 94 100 * uint64(MB), 95 false, 96 }, 97 } 98 for _, tc := range testcases { 99 actual, err := parseFileSize(tc.size) 100 if tc.expectedErr { 101 require.Error(t, err) 102 continue 103 } 104 require.NoError(t, err) 105 require.Equal(t, tc.expected, actual) 106 } 107 } 108 109 func TestParseFileAgeUnit(t *testing.T) { 110 testcases := []struct { 111 age string 112 expected time.Duration 113 expectedErr bool 114 }{ 115 { 116 "1s", 117 1 * time.Second, 118 false, 119 }, 120 { 121 "1sec", 122 1 * time.Second, 123 false, 124 }, 125 { 126 "1S", 127 0, 128 true, 129 }, 130 { 131 "_S", 132 0, 133 true, 134 }, 135 { 136 "_Sec", 137 0, 138 true, 139 }, 140 { 141 "1m", 142 0, 143 true, 144 }, 145 { 146 "1min", 147 1 * time.Minute, 148 false, 149 }, 150 { 151 "1H", 152 1 * time.Hour, 153 false, 154 }, 155 { 156 "1hour", 157 1 * time.Hour, 158 false, 159 }, 160 { 161 "2hours", 162 2 * time.Hour, 163 false, 164 }, 165 { 166 "2Hours", 167 2 * time.Hour, 168 false, 169 }, 170 { 171 "1D", 172 1 * time.Duration(Day), 173 false, 174 }, 175 { 176 "1d", 177 1 * time.Duration(Day), 178 false, 179 }, 180 { 181 "1day", 182 1 * time.Duration(Day), 183 false, 184 }, 185 { 186 "2days", 187 2 * time.Duration(Day), 188 false, 189 }, 190 { 191 "2Days", 192 2 * time.Duration(Day), 193 false, 194 }, 195 } 196 for _, tc := range testcases { 197 actual, err := parseFileAge(tc.age) 198 if tc.expectedErr { 199 require.Error(t, err) 200 continue 201 } 202 require.NoError(t, err) 203 require.Equal(t, tc.expected, actual) 204 } 205 } 206 207 func testRotateLogWriteRunCore(t *testing.T, log *rotateLog) { 208 err := log.initialize() 209 require.NoError(t, err) 210 211 for i := 0; i < 100; i++ { 212 data := []byte(strconv.Itoa(i) + " " + time.Now().UTC().Format(backupDateTimeFormat) + " xlog rolling log write test!\n") 213 _, err = log.Write(data) 214 require.NoError(t, err) 215 } 216 time.Sleep(1 * time.Second) 217 err = log.Close() 218 require.NoError(t, err) 219 } 220 221 func TestRotateLog_Write_Compress(t *testing.T) { 222 nano, err := id.ClassicNanoID(6) 223 require.NoError(t, err) 224 rngLogSuffix := "_" + nano() + "_xlog" 225 rngLogZipSuffix := rngLogSuffix + "s" 226 log := &rotateLog{ 227 fileMaxSize: "1KB", 228 filename: filepath.Base(os.Args[0]) + rngLogSuffix + ".log", 229 fileCompressible: true, 230 fileMaxBackups: 4, 231 fileMaxAge: "3day", 232 fileCompressBatch: 2, 233 fileZipName: filepath.Base(os.Args[0]) + rngLogZipSuffix + ".zip", 234 filePath: os.TempDir(), 235 ctx: context.TODO(), 236 } 237 loop := 2 238 for i := 0; i < loop; i++ { 239 testRotateLogWriteRunCore(t, log) 240 } 241 reader, err := zip.OpenReader(filepath.Join(log.filePath, log.fileZipName)) 242 require.NoError(t, err) 243 require.LessOrEqual(t, int((loop-1)*log.fileMaxBackups), len(reader.File)) 244 reader.Close() 245 removed := testCleanLogFiles(t, os.TempDir(), filepath.Base(os.Args[0])+rngLogSuffix, ".log") 246 require.LessOrEqual(t, log.fileMaxBackups+1, removed) 247 removed = testCleanLogFiles(t, os.TempDir(), filepath.Base(os.Args[0])+rngLogZipSuffix, ".zip") 248 require.Equal(t, 1, removed) 249 } 250 251 func TestRotateLog_Write_Delete(t *testing.T) { 252 nano, err := id.ClassicNanoID(6) 253 require.NoError(t, err) 254 rngLogSuffix := "_" + nano() + "_xlog" 255 log := &rotateLog{ 256 fileMaxSize: "1KB", 257 filename: filepath.Base(os.Args[0]) + rngLogSuffix + ".log", 258 fileCompressible: false, 259 fileMaxBackups: 4, 260 fileMaxAge: "3day", 261 filePath: os.TempDir(), 262 ctx: context.TODO(), 263 } 264 loop := 2 265 for i := 0; i < loop; i++ { 266 testRotateLogWriteRunCore(t, log) 267 } 268 removed := testCleanLogFiles(t, os.TempDir(), filepath.Base(os.Args[0])+rngLogSuffix, ".log") 269 require.Equal(t, log.fileMaxBackups+1, removed) 270 } 271 272 func testCleanLogFiles(t *testing.T, path, namePrefix, nameSuffix string) int { 273 // Walk through the log files and find the expired ones. 274 entries, err := os.ReadDir(path) 275 logInfos := make([]os.FileInfo, 0, 16) 276 if err == nil && len(entries) > 0 { 277 for _, entry := range entries { 278 if !entry.IsDir() { 279 filename := entry.Name() 280 if strings.HasPrefix(filename, namePrefix) && strings.HasSuffix(filename, nameSuffix) { 281 if info, err := entry.Info(); err == nil && info != nil { 282 logInfos = append(logInfos, info) 283 } 284 } 285 } else { 286 if entry.Name() == namePrefix+nameSuffix { 287 if info, err := entry.Info(); err == nil && info != nil { 288 logInfos = append(logInfos, info) 289 } 290 } 291 } 292 } 293 } 294 for _, logInfo := range logInfos { 295 _ = os.Remove(filepath.Join(path, logInfo.Name())) 296 } 297 return len(logInfos) 298 } 299 300 func TestRotateLog_Write_PermissionDeniedAccess(t *testing.T) { 301 rf, err := safeopen.CreateBeneath(os.TempDir(), "rpda.log") 302 require.NoError(t, err) 303 err = rf.Close() 304 require.NoError(t, err) 305 306 err = os.Chmod(filepath.Join(os.TempDir(), "rpda.log"), 0o400) 307 require.NoError(t, err) 308 309 rf, err = safeopen.OpenFileBeneath(os.TempDir(), "rpda.log", os.O_WRONLY|os.O_APPEND, 0o666) 310 require.Error(t, err) // Access denied. 311 require.Nil(t, rf) 312 313 ctx, cancel := context.WithCancel(context.TODO()) 314 log := RotateLog(nil, &FileCoreConfig{}) 315 require.Nil(t, log) 316 log = RotateLog(ctx, nil) 317 require.Nil(t, log) 318 319 log = RotateLog(ctx, &FileCoreConfig{ 320 FileMaxSize: "1KB", 321 Filename: "rpda.log", 322 FilePath: os.TempDir(), 323 FileCompressible: false, 324 FileMaxAge: "100days", 325 FileMaxBackups: 4, 326 }) 327 328 _, err = log.Write([]byte("rotate log permission denied access!")) 329 require.Error(t, err) // Access denied. 330 cancel() 331 err = log.Close() 332 require.NoError(t, err) 333 time.Sleep(20 * time.Millisecond) 334 _, err = log.Write([]byte("rotate log permission denied access!")) 335 require.True(t, errors.Is(err, io.EOF)) 336 337 removed := testCleanLogFiles(t, os.TempDir(), "rpda", ".log") 338 require.Equal(t, 1, removed) 339 } 340 341 func TestRotateLog_Write_Dir(t *testing.T) { 342 err := os.Mkdir(filepath.Join(os.TempDir(), "rpda2.log"), 0o600) 343 require.NoError(t, err) 344 345 log := &rotateLog{ 346 fileMaxSize: "1KB", 347 filename: "rpda2.log", 348 fileCompressible: false, 349 fileMaxBackups: 4, 350 fileMaxAge: "3day", 351 filePath: os.TempDir(), 352 ctx: context.TODO(), 353 } 354 355 _, err = log.Write([]byte("rotate log write dir!")) 356 require.Error(t, err) 357 err = log.Close() 358 require.NoError(t, err) 359 360 removed := testCleanLogFiles(t, os.TempDir(), "rpda2", ".log") 361 require.Equal(t, 1, removed) 362 } 363 364 func TestRotateLog_Write_OtherErrors(t *testing.T) { 365 log := &rotateLog{ 366 fileMaxSize: "1KB", 367 filename: "rpda3.log", 368 fileCompressible: false, 369 fileMaxBackups: 4, 370 fileMaxAge: "3day", 371 filePath: os.TempDir(), 372 ctx: context.TODO(), 373 } 374 375 err := log.openOrCreate() 376 require.NoError(t, err) 377 err = log.Close() 378 require.NoError(t, err) 379 380 log.filePath = "abc" 381 err = log.openOrCreate() 382 require.Error(t, err) 383 384 removed := testCleanLogFiles(t, os.TempDir(), "rpda3", ".log") 385 require.Equal(t, 1, removed) 386 }