passenger 5.2.1 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +8 -0
  3. data/CODE_OF_CONDUCT.md +52 -0
  4. data/README.md +17 -9
  5. data/build/agent.rb +3 -1
  6. data/build/cxx_tests.rb +1 -0
  7. data/build/schema_printer.rb +1 -0
  8. data/build/support/cxx_dependency_map.rb +338 -31
  9. data/dev/configkit-schemas/index.json +64 -15
  10. data/dev/copy_boost_headers +1 -0
  11. data/images/justin.png +0 -0
  12. data/images/passenger_logo.svg +45 -0
  13. data/images/spark.png +0 -0
  14. data/resources/templates/standalone/http.erb +4 -0
  15. data/src/agent/AgentMain.cpp +4 -0
  16. data/src/agent/Core/AdminPanelConnector.h +133 -5
  17. data/src/agent/Core/ApplicationPool/Implementation.cpp +1 -0
  18. data/src/agent/Core/ApplicationPool/Options.h +7 -1
  19. data/src/agent/Core/ApplicationPool/Pool.h +1 -0
  20. data/src/agent/Core/ApplicationPool/Pool/GroupUtils.cpp +11 -0
  21. data/src/agent/Core/ApplicationPool/Process.cpp +52 -0
  22. data/src/agent/Core/ApplicationPool/Process.h +4 -8
  23. data/src/agent/Core/Config.h +6 -2
  24. data/src/agent/Core/ConfigChange.cpp +12 -1
  25. data/src/agent/Core/ConfigChange.h +3 -0
  26. data/src/agent/Core/Controller/Config.h +1 -1
  27. data/src/agent/Core/Controller/InitRequest.cpp +1 -1
  28. data/src/agent/Core/Controller/InternalUtils.cpp +2 -2
  29. data/src/agent/Core/CoreMain.cpp +18 -5
  30. data/src/agent/Core/SpawningKit/BackgroundIOCapturer.h +8 -4
  31. data/src/agent/Core/SpawningKit/DirectSpawner.h +3 -1
  32. data/src/agent/Core/SpawningKit/PipeWatcher.h +9 -4
  33. data/src/agent/Core/SpawningKit/SmartSpawner.h +5 -3
  34. data/src/agent/Core/SpawningKit/Spawner.h +1 -1
  35. data/src/agent/ExecHelper/ExecHelperMain.cpp +295 -0
  36. data/src/agent/Shared/Fundamentals/Initialization.cpp +11 -8
  37. data/src/agent/Shared/Fundamentals/Initialization.h +2 -2
  38. data/src/agent/Watchdog/Config.h +5 -2
  39. data/src/apache2_module/Config.cpp +13 -0
  40. data/src/apache2_module/ConfigGeneral/AutoGeneratedDefinitions.cpp +30 -0
  41. data/src/apache2_module/ConfigGeneral/AutoGeneratedSetterFuncs.cpp +90 -0
  42. data/src/apache2_module/ConfigGeneral/ManifestGeneration.h +18 -2
  43. data/src/apache2_module/DirConfig/AutoGeneratedCreateFunction.cpp +5 -0
  44. data/src/apache2_module/DirConfig/AutoGeneratedManifestGeneration.cpp +12 -0
  45. data/src/apache2_module/DirConfig/AutoGeneratedMergeFunction.cpp +7 -0
  46. data/src/apache2_module/DirConfig/AutoGeneratedStruct.h +13 -0
  47. data/src/apache2_module/Hooks.cpp +4 -0
  48. data/src/apache2_module/ServerConfig/AutoGeneratedManifestGeneration.cpp +55 -0
  49. data/src/apache2_module/ServerConfig/AutoGeneratedStruct.h +65 -0
  50. data/src/cxx_supportlib/BackgroundEventLoop.cpp +3 -3
  51. data/src/cxx_supportlib/ConfigKit/Schema.h +53 -31
  52. data/src/cxx_supportlib/ConfigKit/Store.h +12 -8
  53. data/src/cxx_supportlib/Constants.h +2 -1
  54. data/src/cxx_supportlib/DataStructures/StringKeyTable.h +4 -0
  55. data/src/cxx_supportlib/FileTools/PathManipCBindings.cpp +22 -1
  56. data/src/cxx_supportlib/FileTools/PathManipCBindings.h +3 -1
  57. data/src/cxx_supportlib/LoggingKit/Config.h +2 -0
  58. data/src/cxx_supportlib/LoggingKit/Context.h +28 -0
  59. data/src/cxx_supportlib/LoggingKit/Forward.h +0 -1
  60. data/src/cxx_supportlib/LoggingKit/Implementation.cpp +112 -9
  61. data/src/cxx_supportlib/LoggingKit/Logging.h +4 -2
  62. data/src/cxx_supportlib/WebSocketCommandReverseServer.h +34 -43
  63. data/src/cxx_supportlib/vendor-modified/boost/call_traits.hpp +20 -0
  64. data/src/cxx_supportlib/vendor-modified/boost/circular_buffer.hpp +62 -0
  65. data/src/cxx_supportlib/vendor-modified/boost/circular_buffer/base.hpp +3123 -0
  66. data/src/cxx_supportlib/vendor-modified/boost/circular_buffer/debug.hpp +248 -0
  67. data/src/cxx_supportlib/vendor-modified/boost/circular_buffer/details.hpp +498 -0
  68. data/src/cxx_supportlib/vendor-modified/boost/circular_buffer/space_optimized.hpp +1719 -0
  69. data/src/cxx_supportlib/vendor-modified/boost/circular_buffer_fwd.hpp +43 -0
  70. data/src/cxx_supportlib/vendor-modified/boost/detail/call_traits.hpp +172 -0
  71. data/src/nginx_module/ConfigGeneral/AutoGeneratedDefinitions.c +48 -0
  72. data/src/nginx_module/ConfigGeneral/AutoGeneratedSetterFuncs.c +72 -0
  73. data/src/nginx_module/ConfigGeneral/ManifestGeneration.c +32 -0
  74. data/src/nginx_module/ConfigGeneral/ManifestGeneration.h +3 -0
  75. data/src/nginx_module/Configuration.c +25 -0
  76. data/src/nginx_module/ContentHandler.c +42 -4
  77. data/src/nginx_module/LocationConfig/AutoGeneratedCreateFunction.c +5 -0
  78. data/src/nginx_module/LocationConfig/AutoGeneratedManifestGeneration.c +13 -0
  79. data/src/nginx_module/LocationConfig/AutoGeneratedMergeFunction.c +5 -0
  80. data/src/nginx_module/LocationConfig/AutoGeneratedStruct.h +4 -0
  81. data/src/nginx_module/MainConfig/AutoGeneratedCreateFunction.c +30 -0
  82. data/src/nginx_module/MainConfig/AutoGeneratedManifestGeneration.c +60 -0
  83. data/src/nginx_module/MainConfig/AutoGeneratedStruct.h +20 -0
  84. data/src/nginx_module/ngx_http_passenger_module.c +4 -0
  85. data/src/ruby_supportlib/phusion_passenger.rb +1 -1
  86. data/src/ruby_supportlib/phusion_passenger/apache2/config_options.rb +37 -1
  87. data/src/ruby_supportlib/phusion_passenger/constants.rb +1 -0
  88. data/src/ruby_supportlib/phusion_passenger/nginx/config_options.rb +42 -1
  89. data/src/ruby_supportlib/phusion_passenger/packaging.rb +2 -0
  90. data/src/ruby_supportlib/phusion_passenger/platform_info/crypto.rb +13 -3
  91. data/src/ruby_supportlib/phusion_passenger/standalone/config_options_list.rb +20 -0
  92. metadata +16 -2
@@ -167,7 +167,7 @@ pollLibuv(BackgroundEventLoop *bg) {
167
167
 
168
168
  fd = uv_backend_fd(&bg->priv->libuv_loop);
169
169
 
170
- while (!this_thread::interruption_requested()) {
170
+ while (!boost::this_thread::interruption_requested()) {
171
171
  timeout = uv_backend_timeout(&bg->priv->libuv_loop);
172
172
 
173
173
  ctx->syscall_interruption_lock.unlock();
@@ -203,13 +203,13 @@ pollLibuv(BackgroundEventLoop *bg) {
203
203
  } while (ret == -1
204
204
  && lastErrno == EINTR
205
205
  && (!boost::this_thread::syscalls_interruptable()
206
- || !(intrRequested = this_thread::interruption_requested())));
206
+ || !(intrRequested = boost::this_thread::interruption_requested())));
207
207
 
208
208
  ctx->syscall_interruption_lock.lock();
209
209
 
210
210
  if (ret == -1
211
211
  && lastErrno == EINTR
212
- && this_thread::syscalls_interruptable()
212
+ && boost::this_thread::syscalls_interruptable()
213
213
  && intrRequested)
214
214
  {
215
215
  throw boost::thread_interrupted();
@@ -74,44 +74,65 @@ public:
74
74
  inspectFilter(_inspectFilter)
75
75
  { }
76
76
 
77
- Json::Value typecastValue(const Json::Value &val) const {
77
+ bool tryTypecastValue(const Json::Value &val, Json::Value &result) const {
78
78
  if (val.isNull()) {
79
- return Json::nullValue;
79
+ result = Json::nullValue;
80
+ return true;
80
81
  }
81
82
 
82
83
  switch (type) {
83
84
  case STRING_TYPE:
84
- if (val.isString()) {
85
- return val;
85
+ if (val.isConvertibleTo(Json::stringValue)) {
86
+ result = val.asString();
87
+ return true;
86
88
  } else {
87
- return val.asString();
89
+ return false;
88
90
  }
89
91
  case INT_TYPE:
90
- if (val.isInt()) {
91
- return val;
92
+ if (val.isConvertibleTo(Json::intValue)) {
93
+ result = val.asInt();
94
+ return true;
92
95
  } else {
93
- return val.asInt();
96
+ return false;
94
97
  }
95
98
  case UINT_TYPE:
96
- if (val.isUInt()) {
97
- return val;
99
+ if (val.isConvertibleTo(Json::uintValue)) {
100
+ result = val.asUInt();
101
+ return true;
98
102
  } else {
99
- return val.asUInt();
103
+ return false;
100
104
  }
101
105
  case FLOAT_TYPE:
102
- if (val.isDouble()) {
103
- return val;
106
+ if (val.isConvertibleTo(Json::realValue)) {
107
+ result = val.asDouble();
108
+ return true;
104
109
  } else {
105
- return val.asDouble();
110
+ return false;
106
111
  }
107
112
  case BOOL_TYPE:
108
- if (val.isBool()) {
109
- return val;
113
+ if (val.isConvertibleTo(Json::booleanValue)) {
114
+ result = val.asBool();
115
+ return true;
110
116
  } else {
111
- return val.asBool();
117
+ return false;
118
+ }
119
+ case ARRAY_TYPE:
120
+ if (val.isConvertibleTo(Json::arrayValue)) {
121
+ result = val;
122
+ return true;
123
+ } else {
124
+ return false;
125
+ }
126
+ case OBJECT_TYPE:
127
+ if (val.isConvertibleTo(Json::objectValue)) {
128
+ result = val;
129
+ return true;
130
+ } else {
131
+ return false;
112
132
  }
113
133
  default:
114
- return val;
134
+ result = val;
135
+ return true;
115
136
  }
116
137
  }
117
138
 
@@ -321,10 +342,11 @@ public:
321
342
  * configuration store -- to the given configuration key and value.
322
343
  * Validators added with `addValidator()` won't be applied.
323
344
  *
324
- * Returns whether validation passed. If not, then `error` is set.
345
+ * Returns whether validation passed. If not, then an Error is appended
346
+ * to `errors`.
325
347
  */
326
348
  bool validateValue(const HashedStaticString &key, const Json::Value &value,
327
- Error &error) const
349
+ vector<Error> &errors) const
328
350
  {
329
351
  const Entry *entry;
330
352
 
@@ -335,7 +357,7 @@ public:
335
357
 
336
358
  if (value.isNull()) {
337
359
  if (entry->flags & REQUIRED) {
338
- error = Error("'{{" + key + "}}' is required");
360
+ errors.push_back(Error("'{{" + key + "}}' is required"));
339
361
  return false;
340
362
  } else {
341
363
  return true;
@@ -347,14 +369,14 @@ public:
347
369
  if (value.isConvertibleTo(Json::stringValue)) {
348
370
  return true;
349
371
  } else {
350
- error = Error("'{{" + key + "}}' must be a string");
372
+ errors.push_back(Error("'{{" + key + "}}' must be a string"));
351
373
  return false;
352
374
  }
353
375
  case INT_TYPE:
354
376
  if (value.isConvertibleTo(Json::intValue)) {
355
377
  return true;
356
378
  } else {
357
- error = Error("'{{" + key + "}}' must be an integer");
379
+ errors.push_back(Error("'{{" + key + "}}' must be an integer"));
358
380
  return false;
359
381
  }
360
382
  case UINT_TYPE:
@@ -362,32 +384,32 @@ public:
362
384
  if (value.isConvertibleTo(Json::uintValue)) {
363
385
  return true;
364
386
  } else {
365
- error = Error("'{{" + key + "}}' must be greater than 0");
387
+ errors.push_back(Error("'{{" + key + "}}' must be greater than 0"));
366
388
  return false;
367
389
  }
368
390
  } else {
369
- error = Error("'{{" + key + "}}' must be an integer");
391
+ errors.push_back(Error("'{{" + key + "}}' must be an integer"));
370
392
  return false;
371
393
  }
372
394
  case FLOAT_TYPE:
373
395
  if (value.isConvertibleTo(Json::realValue)) {
374
396
  return true;
375
397
  } else {
376
- error = Error("'{{" + key + "}}' must be a number");
398
+ errors.push_back(Error("'{{" + key + "}}' must be a number"));
377
399
  return false;
378
400
  }
379
401
  case BOOL_TYPE:
380
402
  if (value.isConvertibleTo(Json::booleanValue)) {
381
403
  return true;
382
404
  } else {
383
- error = Error("'{{" + key + "}}' must be a boolean");
405
+ errors.push_back(Error("'{{" + key + "}}' must be a boolean"));
384
406
  return false;
385
407
  }
386
408
  case ARRAY_TYPE:
387
409
  if (value.isConvertibleTo(Json::arrayValue)) {
388
410
  return true;
389
411
  } else {
390
- error = Error("'{{" + key + "}}' must be an array");
412
+ errors.push_back(Error("'{{" + key + "}}' must be an array"));
391
413
  return false;
392
414
  }
393
415
  case STRING_ARRAY_TYPE:
@@ -395,20 +417,20 @@ public:
395
417
  Json::Value::const_iterator it, end = value.end();
396
418
  for (it = value.begin(); it != end; it++) {
397
419
  if (it->type() != Json::stringValue) {
398
- error = Error("'{{" + key + "}}' may only contain strings");
420
+ errors.push_back(Error("'{{" + key + "}}' may only contain strings"));
399
421
  return false;
400
422
  }
401
423
  }
402
424
  return true;
403
425
  } else {
404
- error = Error("'{{" + key + "}}' must be an array");
426
+ errors.push_back(Error("'{{" + key + "}}' must be an array"));
405
427
  return false;
406
428
  }
407
429
  case OBJECT_TYPE:
408
430
  if (value.isObject()) {
409
431
  return true;
410
432
  } else {
411
- error = Error("'{{" + key + "}}' must be a JSON object");
433
+ errors.push_back(Error("'{{" + key + "}}' must be a JSON object"));
412
434
  return false;
413
435
  }
414
436
  case ANY_TYPE:
@@ -385,14 +385,17 @@ public:
385
385
  StringKeyTable<Entry>::Iterator p_it(storeWithPreviewData.entries);
386
386
  StringKeyTable<Entry>::ConstIterator it(entries);
387
387
  vector<Error> tmpErrors;
388
- Error error;
389
388
 
390
389
  while (*p_it != NULL) {
391
390
  const HashedStaticString &key = p_it.getKey();
392
391
  Entry &entry = p_it.getValue();
393
392
 
394
393
  if (isWritable(entry) && updates.isMember(key)) {
395
- entry.userValue = updates[key];
394
+ bool ok = entry.schemaEntry->tryTypecastValue(
395
+ updates[key], entry.userValue);
396
+ if (!ok) {
397
+ entry.userValue = updates[key];
398
+ }
396
399
  }
397
400
 
398
401
  p_it.next();
@@ -406,7 +409,11 @@ public:
406
409
  entry.schemaEntry->inspect(subdoc);
407
410
 
408
411
  if (isWritable(entry) && updates.isMember(key)) {
409
- subdoc["user_value"] = updates[key];
412
+ bool ok = entry.schemaEntry->tryTypecastValue(updates[key],
413
+ subdoc["user_value"]);
414
+ if (!ok) {
415
+ subdoc["user_value"] = updates[key];
416
+ }
410
417
  } else {
411
418
  subdoc["user_value"] = entry.userValue;
412
419
  }
@@ -419,9 +426,7 @@ public:
419
426
  subdoc["effective_value"] =
420
427
  getEffectiveValue(subdoc["user_value"],
421
428
  subdoc["default_value"]);
422
- if (!schema->validateValue(it.getKey(), effectiveValue, error)) {
423
- tmpErrors.push_back(error);
424
- }
429
+ schema->validateValue(it.getKey(), effectiveValue, tmpErrors);
425
430
 
426
431
  result[it.getKey()] = subdoc;
427
432
  it.next();
@@ -469,8 +474,7 @@ public:
469
474
  if (isWritable(entry)) {
470
475
  const Json::Value &subdoc =
471
476
  const_cast<const Json::Value &>(preview)[it.getKey()];
472
- entry.userValue = entry.schemaEntry->typecastValue(
473
- subdoc["user_value"]);
477
+ entry.userValue = subdoc["user_value"];
474
478
  }
475
479
  it.next();
476
480
  }
@@ -74,13 +74,14 @@
74
74
  #define FEEDBACK_FD 3
75
75
  #define FLYING_PASSENGER_NAME "Flying Passenger"
76
76
  #define GLOBAL_NAMESPACE_DIRNAME "passenger"
77
+ #define LOG_MONITORING_MAX_LINES 200
77
78
  #define MESSAGE_SERVER_MAX_PASSWORD_SIZE 100
78
79
  #define MESSAGE_SERVER_MAX_USERNAME_SIZE 100
79
80
  #define PASSENGER_API_VERSION "0.3"
80
81
  #define PASSENGER_API_VERSION_MAJOR 0
81
82
  #define PASSENGER_API_VERSION_MINOR 3
82
83
  #define PASSENGER_DEFAULT_USER "nobody"
83
- #define PASSENGER_VERSION "5.2.1"
84
+ #define PASSENGER_VERSION "5.2.2"
84
85
  #define POOL_HELPER_THREAD_STACK_SIZE 262144
85
86
  #define PROCESS_SHUTDOWN_TIMEOUT 60
86
87
  #define PROCESS_SHUTDOWN_TIMEOUT_DISPLAY "1 minute"
@@ -386,6 +386,10 @@ public:
386
386
  }
387
387
  }
388
388
 
389
+ bool contains(const HashedStaticString &key) const {
390
+ return (lookupCell(key) != NULL);
391
+ }
392
+
389
393
  bool lookup(const HashedStaticString &key, const T **result) const {
390
394
  const Cell * const cell = lookupCell(key);
391
395
  if (cell != NULL) {
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2010-2017 Phusion Holding B.V.
3
+ * Copyright (c) 2010-2018 Phusion Holding B.V.
4
4
  *
5
5
  * "Passenger", "Phusion Passenger" and "Union Station" are registered
6
6
  * trademarks of Phusion Holding B.V.
@@ -26,7 +26,9 @@
26
26
 
27
27
  #include <FileTools/PathManipCBindings.h>
28
28
  #include <FileTools/PathManip.h>
29
+ #include <Exceptions.h>
29
30
  #include <cstring>
31
+ #include <cerrno>
30
32
 
31
33
  using namespace std;
32
34
  using namespace Passenger;
@@ -47,6 +49,25 @@ psg_absolutize_path(const char *path, size_t path_len,
47
49
  return strdup(result.c_str());
48
50
  }
49
51
 
52
+ char *
53
+ psg_resolve_symlink(const char *path, size_t path_len,
54
+ size_t *result_len)
55
+ {
56
+ try {
57
+ string result = resolveSymlink(StaticString(path, path_len));
58
+ if (result_len != NULL) {
59
+ *result_len = result.size();
60
+ }
61
+ return strdup(result.c_str());
62
+ } catch (const SystemException &e) {
63
+ errno = e.code();
64
+ return NULL;
65
+ } catch (const std::bad_alloc &) {
66
+ errno = ENOMEM;
67
+ return NULL;
68
+ }
69
+ }
70
+
50
71
  const char *
51
72
  psg_extract_dir_name_static(const char *path,
52
73
  size_t path_len, size_t *result_len)
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * Phusion Passenger - https://www.phusionpassenger.com/
3
- * Copyright (c) 2017 Phusion Holding B.V.
3
+ * Copyright (c) 2018 Phusion Holding B.V.
4
4
  *
5
5
  * "Passenger", "Phusion Passenger" and "Union Station" are registered
6
6
  * trademarks of Phusion Holding B.V.
@@ -36,6 +36,8 @@ extern "C" {
36
36
  char *psg_absolutize_path(const char *path, size_t path_len,
37
37
  const char *working_dir, size_t working_dir_len,
38
38
  size_t *result_len);
39
+ char *psg_resolve_symlink(const char *path, size_t path_len,
40
+ size_t *result_len);
39
41
  const char *psg_extract_dir_name_static(const char *path,
40
42
  size_t path_len, size_t *result_len);
41
43
 
@@ -50,6 +50,7 @@ using namespace std;
50
50
  * by 'rake configkit_schemas_inline_comments')
51
51
  *
52
52
  * app_output_log_level string - default("notice")
53
+ * buffer_logs boolean - default(false)
53
54
  * file_descriptor_log_target any - -
54
55
  * level string - default("notice")
55
56
  * redirect_stderr boolean - default(true)
@@ -82,6 +83,7 @@ struct ConfigRealization {
82
83
  TargetType targetType;
83
84
  TargetType fileDescriptorLogTargetType;
84
85
  int targetFd;
86
+ bool saveLog;
85
87
  int fileDescriptorLogTargetFd;
86
88
  FdClosePolicy targetFdClosePolicy;
87
89
  FdClosePolicy fileDescriptorLogTargetFdClosePolicy;
@@ -28,14 +28,17 @@
28
28
 
29
29
  #include <queue>
30
30
 
31
+ #include <jsoncpp/json.h>
31
32
  #include <oxt/macros.hpp>
32
33
  #include <oxt/thread.hpp>
34
+ #include <boost/circular_buffer.hpp>
33
35
  #include <boost/thread.hpp>
34
36
  #include <boost/atomic.hpp>
35
37
  #include <ConfigKit/ConfigKit.h>
36
38
  #include <LoggingKit/Forward.h>
37
39
  #include <LoggingKit/Config.h>
38
40
  #include <Utils/SystemTime.h>
41
+ #include <DataStructures/StringKeyTable.h>
39
42
 
40
43
  namespace Passenger {
41
44
  namespace LoggingKit {
@@ -78,12 +81,37 @@ private:
78
81
  queue< pair<ConfigRealization *, MonotonicTimeUsec> > oldConfigs;
79
82
  bool shuttingDown;
80
83
 
84
+ struct TimestampedLog {
85
+ // time at which time the log entered the core, which is unfortunately somewhat
86
+ // arbitrarily later than that it was logged in the user program
87
+ unsigned long long timestamp;
88
+ string sourceId;
89
+ string lineText;
90
+ };
91
+ typedef boost::circular_buffer<TimestampedLog> TimestampedLogBuffer;
92
+
93
+ typedef boost::circular_buffer<string> SimpleLogBuffer;
94
+ typedef StringKeyTable<SimpleLogBuffer> SimpleLogMap;
95
+ struct AppGroupLog {
96
+ TimestampedLogBuffer pidLog; // combined logs from PIDs
97
+ SimpleLogMap watchFileLog; // a separate log buffer per (watched file name)
98
+ };
99
+ typedef StringKeyTable<AppGroupLog> LogStore;
100
+ LogStore logStore;
101
+
81
102
  public:
82
103
  Context(const Json::Value &initialConfig = Json::Value(),
83
104
  const ConfigKit::Translator &translator = ConfigKit::DummyTranslator());
84
105
  ~Context();
85
106
  ConfigKit::Store getConfig() const;
86
107
 
108
+ // specifically for logging output from application processes
109
+ void saveNewLog(const HashedStaticString &groupName, const char *sourceStr, unsigned int sourceStrLen, const char *message, unsigned int messageLen);
110
+ // the message might already have been logged (e.g. because the source is a `tail -n`)
111
+ void updateLog(const HashedStaticString &groupName, const char *sourceStr, unsigned int sourceStrLen, const char *message, unsigned int messageLen);
112
+ // snapshot logStore to a JSON structure for external relay
113
+ Json::Value convertLog();
114
+
87
115
  bool prepareConfigChange(const Json::Value &updates,
88
116
  vector<ConfigKit::Error> &errors,
89
117
  LoggingKit::ConfigChangeRequest &req);
@@ -73,7 +73,6 @@ enum TargetType {
73
73
 
74
74
  extern Context *context;
75
75
 
76
-
77
76
  void shutdown();
78
77
 
79
78
  const char *_strdupFastStringStream(const FastStringStream<> &stream);
@@ -41,7 +41,8 @@
41
41
  #include <pthread.h>
42
42
 
43
43
  #include <boost/cstdint.hpp>
44
- #include <oxt/system_calls.hpp>
44
+ #include <boost/circular_buffer.hpp>
45
+ #include <boost/foreach.hpp>
45
46
  #include <oxt/thread.hpp>
46
47
  #include <oxt/detail/context.hpp>
47
48
 
@@ -69,7 +70,6 @@ using namespace std;
69
70
  Context *context = NULL;
70
71
  AssertionFailureInfo lastAssertionFailure;
71
72
 
72
-
73
73
  void
74
74
  initialize(const Json::Value &initialConfig, const ConfigKit::Translator &translator) {
75
75
  context = new Context(initialConfig, translator);
@@ -309,11 +309,94 @@ _writeFileDescriptorLogEntry(const ConfigRealization *configRealization,
309
309
  writeExactWithoutOXT(configRealization->fileDescriptorLogTargetFd, str, size);
310
310
  }
311
311
 
312
+ void
313
+ Context::saveNewLog(const HashedStaticString &groupName, const char *sourceStr, unsigned int sourceStrLen, const char *message, unsigned int messageLen) {
314
+ boost::lock_guard<boost::mutex> l(syncher); //lock
315
+
316
+ unsigned long long timestamp = SystemTime::getUsec();
317
+
318
+ LogStore::Cell *c = logStore.lookupCell(groupName);
319
+ if (c == NULL) {
320
+ AppGroupLog appGroupLog;
321
+ appGroupLog.pidLog = TimestampedLogBuffer(LOG_MONITORING_MAX_LINES * 5);
322
+ c = logStore.insert(groupName, appGroupLog);
323
+ }
324
+ AppGroupLog &rec = c->value;
325
+
326
+ TimestampedLog ll;
327
+ ll.timestamp = timestamp;
328
+ ll.sourceId = string(sourceStr, sourceStrLen);
329
+ ll.lineText = string(message, messageLen);
330
+ rec.pidLog.push_back(ll);
331
+ //unlock
332
+ }
333
+
334
+ void
335
+ Context::updateLog(const HashedStaticString &groupName, const char *sourceStr, unsigned int sourceStrLen, const char *message, unsigned int messageLen) {
336
+ boost::lock_guard<boost::mutex> l(syncher); //lock
337
+
338
+ LogStore::Cell *c = logStore.lookupCell(groupName);
339
+ if (c == NULL) {
340
+ AppGroupLog appGroupLog;
341
+ appGroupLog.pidLog = TimestampedLogBuffer(LOG_MONITORING_MAX_LINES * 5);
342
+ c = logStore.insert(groupName, appGroupLog);
343
+ }
344
+ AppGroupLog &rec = c->value;
345
+
346
+ HashedStaticString source(sourceStr, sourceStrLen);
347
+ if (!rec.watchFileLog.contains(source)) {
348
+ SimpleLogBuffer logBuffer(LOG_MONITORING_MAX_LINES);
349
+ rec.watchFileLog.insert(source, logBuffer);
350
+ }
351
+ rec.watchFileLog.lookupCell(source)->value.push_back(string(message, messageLen));
352
+ //unlock
353
+ }
354
+
355
+ Json::Value
356
+ Context::convertLog(){
357
+ boost::lock_guard<boost::mutex> l(syncher); //lock
358
+ Json::Value reply = Json::objectValue;
359
+
360
+ if (!logStore.empty()) {
361
+ Context::LogStore::ConstIterator appGroupIter(logStore);
362
+ while (*appGroupIter != NULL) {
363
+ reply[appGroupIter.getKey()] = Json::objectValue;
364
+
365
+ Json::Value &processLog = reply[appGroupIter.getKey()]["Application process log (combined)"];
366
+ foreach (TimestampedLog logLine, appGroupIter->value.pidLog) {
367
+ Json::Value logLineJson = Json::objectValue;
368
+ logLineJson["source_id"] = logLine.sourceId;
369
+ logLineJson["timestamp"] = (Json::UInt64) logLine.timestamp;
370
+ logLineJson["line"] = logLine.lineText;
371
+ processLog.append(logLineJson);
372
+ }
373
+
374
+ Context::SimpleLogMap::ConstIterator watchFileLogIter(appGroupIter->value.watchFileLog);
375
+ while (*watchFileLogIter != NULL) {
376
+ if (!reply[appGroupIter.getKey()].isMember(watchFileLogIter.getKey())){
377
+ reply[appGroupIter.getKey()][watchFileLogIter.getKey()] = Json::arrayValue;
378
+ }
379
+ foreach (string line, watchFileLogIter->value) {
380
+ reply[appGroupIter.getKey()][watchFileLogIter.getKey()].append(line);
381
+ }
382
+ watchFileLogIter.next();
383
+ }
384
+
385
+ appGroupIter.next();
386
+ }
387
+ }
388
+
389
+ return reply;
390
+ //unlock
391
+ }
392
+
312
393
  static void
313
- realLogAppOutput(int targetFd, char *buf, unsigned int bufSize,
394
+ realLogAppOutput(const HashedStaticString &groupName, int targetFd,
395
+ char *buf, unsigned int bufSize,
314
396
  const char *pidStr, unsigned int pidStrLen,
315
397
  const char *channelName, unsigned int channelNameLen,
316
- const char *message, unsigned int messageLen)
398
+ const char *message, unsigned int messageLen, int appLogFile,
399
+ bool saveLog)
317
400
  {
318
401
  char *pos = buf;
319
402
  char *end = buf + bufSize;
@@ -325,12 +408,20 @@ realLogAppOutput(int targetFd, char *buf, unsigned int bufSize,
325
408
  pos = appendData(pos, end, ": ");
326
409
  pos = appendData(pos, end, message, messageLen);
327
410
  pos = appendData(pos, end, "\n");
411
+
412
+ if (OXT_UNLIKELY(context != NULL && saveLog)) {
413
+ context->saveNewLog(groupName, pidStr, pidStrLen, message, messageLen);
414
+ }
415
+ if (appLogFile > -1) {
416
+ writeExactWithoutOXT(appLogFile, buf, pos - buf);
417
+ }
328
418
  writeExactWithoutOXT(targetFd, buf, pos - buf);
329
419
  }
330
420
 
331
421
  void
332
- logAppOutput(pid_t pid, const char *channelName, const char *message, unsigned int size) {
422
+ logAppOutput(const HashedStaticString &groupName, pid_t pid, const char *channelName, const char *message, unsigned int size, const StaticString &appLogFile) {
333
423
  int targetFd;
424
+ bool saveLog = false;
334
425
 
335
426
  if (OXT_LIKELY(context != NULL)) {
336
427
  const ConfigRealization *configRealization = context->getConfigRealization();
@@ -339,10 +430,19 @@ logAppOutput(pid_t pid, const char *channelName, const char *message, unsigned i
339
430
  }
340
431
 
341
432
  targetFd = configRealization->targetFd;
433
+ saveLog = configRealization->saveLog;
342
434
  } else {
343
435
  targetFd = STDERR_FILENO;
344
436
  }
345
437
 
438
+ int fd = -1;
439
+ if (!appLogFile.empty()) {
440
+ fd = open(appLogFile.data(), O_WRONLY | O_APPEND | O_CREAT, 0640);
441
+ if (fd == -1) {
442
+ int e = errno;
443
+ P_ERROR("opening file: " << appLogFile << " for logging " << groupName << " failed. Error: " << strerror(e));
444
+ }
445
+ }
346
446
  char pidStr[sizeof("4294967295")];
347
447
  unsigned int pidStrLen, channelNameLen, totalLen;
348
448
 
@@ -358,19 +458,20 @@ logAppOutput(pid_t pid, const char *channelName, const char *message, unsigned i
358
458
  totalLen = (sizeof("App X Y: \n") - 2) + pidStrLen + channelNameLen + size;
359
459
  if (totalLen < 1024) {
360
460
  char buf[1024];
361
- realLogAppOutput(targetFd,
461
+ realLogAppOutput(groupName, targetFd,
362
462
  buf, sizeof(buf),
363
463
  pidStr, pidStrLen,
364
464
  channelName, channelNameLen,
365
- message, size);
465
+ message, size, fd, saveLog);
366
466
  } else {
367
467
  DynamicBuffer buf(totalLen);
368
- realLogAppOutput(targetFd,
468
+ realLogAppOutput(groupName, targetFd,
369
469
  buf.data, totalLen,
370
470
  pidStr, pidStrLen,
371
471
  channelName, channelNameLen,
372
- message, size);
472
+ message, size, fd, saveLog);
373
473
  }
474
+ if(fd > -1){close(fd);}
374
475
  }
375
476
 
376
477
 
@@ -662,6 +763,7 @@ Schema::Schema() {
662
763
  .setInspectFilter(filterTargetFd);
663
764
  add("redirect_stderr", BOOL_TYPE, OPTIONAL, true);
664
765
  add("app_output_log_level", STRING_TYPE, OPTIONAL, DEFAULT_APP_OUTPUT_LOG_LEVEL_NAME);
766
+ add("buffer_logs", BOOL_TYPE, OPTIONAL, false);
665
767
 
666
768
  addValidator(boost::bind(validateLogLevel, "level",
667
769
  boost::placeholders::_1, boost::placeholders::_2));
@@ -681,6 +783,7 @@ Schema::Schema() {
681
783
  ConfigRealization::ConfigRealization(const ConfigKit::Store &store)
682
784
  : level(parseLevel(store["level"].asString())),
683
785
  appOutputLogLevel(parseLevel(store["app_output_log_level"].asString())),
786
+ saveLog(store["buffer_logs"].asBool()),
684
787
  finalized(false)
685
788
  {
686
789
  if (store["target"].isMember("stderr")) {