logger.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /*
  2. * Copyright (c) 2025, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package testutils
  20. import (
  21. "encoding/json"
  22. "fmt"
  23. "sync/atomic"
  24. "time"
  25. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  26. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  27. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/stacktrace"
  28. )
  29. type TestLogger struct {
  30. logLevelDebug int32
  31. component string
  32. metricValidator func(string, common.LogFields) bool
  33. packetMetrics chan common.LogFields
  34. packetMetricsTimeout time.Duration
  35. hasValidMetric int32
  36. hasInvalidMetric int32
  37. }
  38. func NewTestLogger() *TestLogger {
  39. return &TestLogger{}
  40. }
  41. func NewTestLoggerWithPacketMetrics(
  42. packetMetricCount int,
  43. packetMetricsTimeout time.Duration) *TestLogger {
  44. return &TestLogger{
  45. packetMetrics: make(chan common.LogFields, packetMetricCount),
  46. packetMetricsTimeout: packetMetricsTimeout,
  47. }
  48. }
  49. func NewTestLoggerWithComponent(
  50. component string) *TestLogger {
  51. return &TestLogger{
  52. component: component,
  53. }
  54. }
  55. func NewTestLoggerWithMetricValidator(
  56. component string,
  57. metricValidator func(string, common.LogFields) bool) *TestLogger {
  58. return &TestLogger{
  59. component: component,
  60. metricValidator: metricValidator,
  61. }
  62. }
  63. func (logger *TestLogger) WithTrace() common.LogTrace {
  64. return &testLoggerTrace{
  65. logger: logger,
  66. trace: stacktrace.GetParentFunctionName(),
  67. }
  68. }
  69. func (logger *TestLogger) WithTraceFields(fields common.LogFields) common.LogTrace {
  70. return &testLoggerTrace{
  71. logger: logger,
  72. trace: stacktrace.GetParentFunctionName(),
  73. fields: fields,
  74. }
  75. }
  76. func (logger *TestLogger) LogMetric(metric string, fields common.LogFields) {
  77. if metric == "server_packet_metrics" && logger.packetMetrics != nil {
  78. select {
  79. case logger.packetMetrics <- fields:
  80. default:
  81. }
  82. }
  83. if logger.metricValidator != nil {
  84. if logger.metricValidator(metric, fields) {
  85. atomic.StoreInt32(&logger.hasValidMetric, 1)
  86. } else {
  87. atomic.StoreInt32(&logger.hasInvalidMetric, 1)
  88. }
  89. // Don't print log.
  90. return
  91. }
  92. jsonFields, _ := json.Marshal(fields)
  93. var component string
  94. if len(logger.component) > 0 {
  95. component = fmt.Sprintf("[%s]", logger.component)
  96. }
  97. fmt.Printf(
  98. "[%s]%s METRIC: %s: %s\n",
  99. time.Now().UTC().Format(time.RFC3339),
  100. component,
  101. metric,
  102. string(jsonFields))
  103. }
  104. func (logger *TestLogger) CheckMetrics(expectValidMetric bool) error {
  105. if expectValidMetric && atomic.LoadInt32(&logger.hasValidMetric) != 1 {
  106. return errors.TraceNew("missing valid metric")
  107. }
  108. if atomic.LoadInt32(&logger.hasInvalidMetric) == 1 {
  109. return errors.TraceNew("has invalid metric")
  110. }
  111. return nil
  112. }
  113. func (logger *TestLogger) GetNextPacketMetrics() common.LogFields {
  114. if logger.packetMetrics == nil {
  115. return nil
  116. }
  117. timer := time.NewTimer(logger.packetMetricsTimeout)
  118. defer timer.Stop()
  119. select {
  120. case fields := <-logger.packetMetrics:
  121. return fields
  122. case <-timer.C:
  123. return nil
  124. }
  125. }
  126. func (logger *TestLogger) IsLogLevelDebug() bool {
  127. return atomic.LoadInt32(&logger.logLevelDebug) == 1
  128. }
  129. func (logger *TestLogger) SetLogLevelDebug(logLevelDebug bool) {
  130. value := int32(0)
  131. if logLevelDebug {
  132. value = 1
  133. }
  134. atomic.StoreInt32(&logger.logLevelDebug, value)
  135. }
  136. type testLoggerTrace struct {
  137. logger *TestLogger
  138. trace string
  139. fields common.LogFields
  140. }
  141. func (logger *testLoggerTrace) log(priority, message string) {
  142. now := time.Now().UTC().Format(time.RFC3339)
  143. var component string
  144. if len(logger.logger.component) > 0 {
  145. component = fmt.Sprintf("[%s]", logger.logger.component)
  146. }
  147. if len(logger.fields) == 0 {
  148. fmt.Printf(
  149. "[%s]%s %s: %s: %s\n",
  150. now, component, priority, logger.trace, message)
  151. } else {
  152. fields := common.LogFields{}
  153. for k, v := range logger.fields {
  154. switch v := v.(type) {
  155. case error:
  156. // Workaround for Go issue 5161: error types marshal to "{}"
  157. fields[k] = v.Error()
  158. default:
  159. fields[k] = v
  160. }
  161. }
  162. jsonFields, _ := json.Marshal(fields)
  163. fmt.Printf(
  164. "[%s]%s %s: %s: %s %s\n",
  165. now, component, priority, logger.trace, message, string(jsonFields))
  166. }
  167. }
  168. func (logger *testLoggerTrace) Debug(args ...interface{}) {
  169. if !logger.logger.IsLogLevelDebug() {
  170. return
  171. }
  172. logger.log("DEBUG", fmt.Sprint(args...))
  173. }
  174. func (logger *testLoggerTrace) Info(args ...interface{}) {
  175. logger.log("INFO", fmt.Sprint(args...))
  176. }
  177. func (logger *testLoggerTrace) Warning(args ...interface{}) {
  178. logger.log("WARNING", fmt.Sprint(args...))
  179. }
  180. func (logger *testLoggerTrace) Error(args ...interface{}) {
  181. logger.log("ERROR", fmt.Sprint(args...))
  182. }