Refactoring: split logging code into individual files
This commit is contained in:
parent
24e8da4474
commit
4c0d706857
|
@ -347,8 +347,18 @@ target_link_libraries(rhubarb-lib
|
||||||
|
|
||||||
# ... rhubarb-logging
|
# ... rhubarb-logging
|
||||||
add_library(rhubarb-logging
|
add_library(rhubarb-logging
|
||||||
|
src/logging/Entry.cpp
|
||||||
|
src/logging/Entry.h
|
||||||
|
src/logging/Formatter.h
|
||||||
|
src/logging/formatters.cpp
|
||||||
|
src/logging/formatters.h
|
||||||
|
src/logging/Level.cpp
|
||||||
|
src/logging/Level.h
|
||||||
src/logging/logging.cpp
|
src/logging/logging.cpp
|
||||||
src/logging/logging.h
|
src/logging/logging.h
|
||||||
|
src/logging/Sink.h
|
||||||
|
src/logging/sinks.cpp
|
||||||
|
src/logging/sinks.h
|
||||||
)
|
)
|
||||||
target_include_directories(rhubarb-logging PUBLIC "src/logging")
|
target_include_directories(rhubarb-logging PUBLIC "src/logging")
|
||||||
target_link_libraries(rhubarb-logging
|
target_link_libraries(rhubarb-logging
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#include "Entry.h"
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
using std::lock_guard;
|
||||||
|
using std::unordered_map;
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
// Returns an int representing the current thread.
|
||||||
|
// This used to be a simple thread_local variable, but Xcode doesn't support that yet
|
||||||
|
int getThreadCounter() {
|
||||||
|
using thread_id = std::thread::id;
|
||||||
|
|
||||||
|
static std::mutex counterMutex;
|
||||||
|
lock_guard<std::mutex> lock(counterMutex);
|
||||||
|
|
||||||
|
static unordered_map<thread_id, int> threadCounters;
|
||||||
|
static int lastThreadId = 0;
|
||||||
|
thread_id threadId = std::this_thread::get_id();
|
||||||
|
if (threadCounters.find(threadId) == threadCounters.end()) {
|
||||||
|
threadCounters.insert({threadId, ++lastThreadId});
|
||||||
|
}
|
||||||
|
return threadCounters.find(threadId)->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry::Entry(Level level, const string& message) :
|
||||||
|
level(level),
|
||||||
|
message(message)
|
||||||
|
{
|
||||||
|
time(×tamp);
|
||||||
|
this->threadCounter = getThreadCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Level.h"
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
Entry(Level level, const std::string& message);
|
||||||
|
|
||||||
|
time_t timestamp;
|
||||||
|
int threadCounter;
|
||||||
|
Level level;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "Entry.h"
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
class Formatter {
|
||||||
|
public:
|
||||||
|
virtual ~Formatter() = default;
|
||||||
|
virtual std::string format(const Entry& entry) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include "Level.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
LevelConverter& LevelConverter::get() {
|
||||||
|
static LevelConverter converter;
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
string LevelConverter::getTypeName() {
|
||||||
|
return "Level";
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumConverter<Level>::member_data LevelConverter::getMemberData() {
|
||||||
|
return member_data{
|
||||||
|
{Level::Trace, "Trace"},
|
||||||
|
{Level::Debug, "Debug"},
|
||||||
|
{Level::Info, "Info"},
|
||||||
|
{Level::Warn, "Warn"},
|
||||||
|
{Level::Error, "Error"},
|
||||||
|
{Level::Fatal, "Fatal"}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& stream, Level value) {
|
||||||
|
return LevelConverter::get().write(stream, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::istream& operator >> (std::istream& stream, Level& value) {
|
||||||
|
return LevelConverter::get().read(stream, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "EnumConverter.h"
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
enum class Level {
|
||||||
|
Trace,
|
||||||
|
Debug,
|
||||||
|
Info,
|
||||||
|
Warn,
|
||||||
|
Error,
|
||||||
|
Fatal,
|
||||||
|
EndSentinel
|
||||||
|
};
|
||||||
|
|
||||||
|
class LevelConverter : public EnumConverter<Level> {
|
||||||
|
public:
|
||||||
|
static LevelConverter& get();
|
||||||
|
protected:
|
||||||
|
std::string getTypeName() override;
|
||||||
|
member_data getMemberData() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& stream, Level value);
|
||||||
|
|
||||||
|
std::istream& operator >> (std::istream& stream, Level& value);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Entry.h"
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
class Sink {
|
||||||
|
public:
|
||||||
|
virtual ~Sink() = default;
|
||||||
|
virtual void receive(const Entry& entry) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
#include "formatters.h"
|
||||||
|
#include <format.h>
|
||||||
|
#include "Entry.h"
|
||||||
|
#include "tools.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
string SimpleConsoleFormatter::format(const Entry& entry) {
|
||||||
|
return fmt::format("[{0}] {1}", entry.level, entry.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
string SimpleFileFormatter::format(const Entry& entry) {
|
||||||
|
return fmt::format("[{0}] {1} {2}", formatTime(entry.timestamp, "%F %H:%M:%S"), entry.threadCounter, consoleFormatter.format(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Formatter.h"
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
class SimpleConsoleFormatter : public Formatter {
|
||||||
|
public:
|
||||||
|
std::string format(const Entry& entry) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SimpleFileFormatter : public Formatter {
|
||||||
|
public:
|
||||||
|
std::string format(const Entry& entry) override;
|
||||||
|
private:
|
||||||
|
SimpleConsoleFormatter consoleFormatter;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -1,130 +1,13 @@
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
#include <tools.h>
|
#include <tools.h>
|
||||||
#include <iostream>
|
#include <mutex>
|
||||||
#include <atomic>
|
#include "Entry.h"
|
||||||
#include <thread>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
using namespace logging;
|
using namespace logging;
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::vector;
|
using std::vector;
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
using std::lock_guard;
|
using std::lock_guard;
|
||||||
using std::unordered_map;
|
|
||||||
|
|
||||||
LevelConverter& LevelConverter::get() {
|
|
||||||
static LevelConverter converter;
|
|
||||||
return converter;
|
|
||||||
}
|
|
||||||
|
|
||||||
string LevelConverter::getTypeName() {
|
|
||||||
return "Level";
|
|
||||||
}
|
|
||||||
|
|
||||||
EnumConverter<Level>::member_data LevelConverter::getMemberData() {
|
|
||||||
return member_data {
|
|
||||||
{ Level::Trace, "Trace" },
|
|
||||||
{ Level::Debug, "Debug" },
|
|
||||||
{ Level::Info, "Info" },
|
|
||||||
{ Level::Warn, "Warn" },
|
|
||||||
{ Level::Error, "Error" },
|
|
||||||
{ Level::Fatal, "Fatal" }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& logging::operator<<(std::ostream& stream, Level value) {
|
|
||||||
return LevelConverter::get().write(stream, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::istream& logging::operator>>(std::istream& stream, Level& value) {
|
|
||||||
return LevelConverter::get().read(stream, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns an int representing the current thread.
|
|
||||||
// This used to be a simple thread_local variable, but Xcode doesn't support that yet
|
|
||||||
int getThreadCounter() {
|
|
||||||
using thread_id = std::thread::id;
|
|
||||||
|
|
||||||
static std::mutex counterMutex;
|
|
||||||
lock_guard<std::mutex> lock(counterMutex);
|
|
||||||
|
|
||||||
static unordered_map<thread_id, int> threadCounters;
|
|
||||||
static int lastThreadId = 0;
|
|
||||||
thread_id threadId = std::this_thread::get_id();
|
|
||||||
if (threadCounters.find(threadId) == threadCounters.end()) {
|
|
||||||
threadCounters.insert({threadId, ++lastThreadId});
|
|
||||||
}
|
|
||||||
return threadCounters.find(threadId)->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
Entry::Entry(Level level, const string& message) :
|
|
||||||
level(level),
|
|
||||||
message(message)
|
|
||||||
{
|
|
||||||
time(×tamp);
|
|
||||||
this->threadCounter = getThreadCounter();
|
|
||||||
}
|
|
||||||
|
|
||||||
string SimpleConsoleFormatter::format(const Entry& entry) {
|
|
||||||
return fmt::format("[{0}] {1}", entry.level, entry.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
string SimpleFileFormatter::format(const Entry& entry) {
|
|
||||||
return fmt::format("[{0}] {1} {2}", formatTime(entry.timestamp, "%F %H:%M:%S"), entry.threadCounter, consoleFormatter.format(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
LevelFilter::LevelFilter(shared_ptr<Sink> innerSink, Level minLevel) :
|
|
||||||
innerSink(innerSink),
|
|
||||||
minLevel(minLevel)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void LevelFilter::receive(const Entry& entry) {
|
|
||||||
if (entry.level >= minLevel) {
|
|
||||||
innerSink->receive(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamSink::StreamSink(shared_ptr<std::ostream> stream, shared_ptr<Formatter> formatter) :
|
|
||||||
stream(stream),
|
|
||||||
formatter(formatter)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void StreamSink::receive(const Entry& entry) {
|
|
||||||
string line = formatter->format(entry);
|
|
||||||
*stream << line << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
StdErrSink::StdErrSink(shared_ptr<Formatter> formatter) :
|
|
||||||
StreamSink(std::shared_ptr<std::ostream>(&std::cerr, [](void*) {}), formatter)
|
|
||||||
{}
|
|
||||||
|
|
||||||
PausableSink::PausableSink(shared_ptr<Sink> innerSink) :
|
|
||||||
innerSink(innerSink)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void PausableSink::receive(const Entry& entry) {
|
|
||||||
lock_guard<std::mutex> lock(mutex);
|
|
||||||
if (isPaused) {
|
|
||||||
buffer.push_back(entry);
|
|
||||||
} else {
|
|
||||||
innerSink->receive(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PausableSink::pause() {
|
|
||||||
lock_guard<std::mutex> lock(mutex);
|
|
||||||
isPaused = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PausableSink::resume() {
|
|
||||||
lock_guard<std::mutex> lock(mutex);
|
|
||||||
isPaused = false;
|
|
||||||
for (const Entry& entry : buffer) {
|
|
||||||
innerSink->receive(entry);
|
|
||||||
}
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::mutex& getLogMutex() {
|
std::mutex& getLogMutex() {
|
||||||
static std::mutex mutex;
|
static std::mutex mutex;
|
||||||
|
|
|
@ -1,103 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <mutex>
|
|
||||||
#include "tools.h"
|
|
||||||
#include "EnumConverter.h"
|
#include "EnumConverter.h"
|
||||||
|
#include "Sink.h"
|
||||||
|
#include "Level.h"
|
||||||
|
|
||||||
namespace logging {
|
namespace logging {
|
||||||
|
|
||||||
enum class Level {
|
|
||||||
Trace,
|
|
||||||
Debug,
|
|
||||||
Info,
|
|
||||||
Warn,
|
|
||||||
Error,
|
|
||||||
Fatal,
|
|
||||||
EndSentinel
|
|
||||||
};
|
|
||||||
|
|
||||||
class LevelConverter : public EnumConverter<Level> {
|
|
||||||
public:
|
|
||||||
static LevelConverter& get();
|
|
||||||
protected:
|
|
||||||
std::string getTypeName() override;
|
|
||||||
member_data getMemberData() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& stream, Level value);
|
|
||||||
|
|
||||||
std::istream& operator>>(std::istream& stream, Level& value);
|
|
||||||
|
|
||||||
struct Entry {
|
|
||||||
Entry(Level level, const std::string& message);
|
|
||||||
|
|
||||||
time_t timestamp;
|
|
||||||
int threadCounter;
|
|
||||||
Level level;
|
|
||||||
std::string message;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Formatter {
|
|
||||||
public:
|
|
||||||
virtual ~Formatter() = default;
|
|
||||||
virtual std::string format(const Entry& entry) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SimpleConsoleFormatter : public Formatter {
|
|
||||||
public:
|
|
||||||
std::string format(const Entry& entry) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SimpleFileFormatter : public Formatter {
|
|
||||||
public:
|
|
||||||
std::string format(const Entry& entry) override;
|
|
||||||
private:
|
|
||||||
SimpleConsoleFormatter consoleFormatter;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Sink {
|
|
||||||
public:
|
|
||||||
virtual ~Sink() = default;
|
|
||||||
virtual void receive(const Entry& entry) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LevelFilter : public Sink {
|
|
||||||
public:
|
|
||||||
LevelFilter(std::shared_ptr<Sink> innerSink, Level minLevel);
|
|
||||||
void receive(const Entry& entry) override;
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Sink> innerSink;
|
|
||||||
Level minLevel;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StreamSink : public Sink {
|
|
||||||
public:
|
|
||||||
StreamSink(std::shared_ptr<std::ostream> stream, std::shared_ptr<Formatter> formatter);
|
|
||||||
void receive(const Entry& entry) override;
|
|
||||||
private:
|
|
||||||
std::shared_ptr<std::ostream> stream;
|
|
||||||
std::shared_ptr<Formatter> formatter;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StdErrSink : public StreamSink {
|
|
||||||
public:
|
|
||||||
explicit StdErrSink(std::shared_ptr<Formatter> formatter);
|
|
||||||
};
|
|
||||||
|
|
||||||
class PausableSink : public Sink {
|
|
||||||
public:
|
|
||||||
explicit PausableSink(std::shared_ptr<Sink> innerSink);
|
|
||||||
void receive(const Entry& entry) override;
|
|
||||||
void pause();
|
|
||||||
void resume();
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Sink> innerSink;
|
|
||||||
std::vector<Entry> buffer;
|
|
||||||
std::mutex mutex;
|
|
||||||
bool isPaused = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
void addSink(std::shared_ptr<Sink> sink);
|
void addSink(std::shared_ptr<Sink> sink);
|
||||||
|
|
||||||
void log(Level level, const std::string& message);
|
void log(Level level, const std::string& message);
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
#include "sinks.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include "Entry.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using std::lock_guard;
|
||||||
|
using std::shared_ptr;
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
|
||||||
|
LevelFilter::LevelFilter(shared_ptr<Sink> innerSink, Level minLevel) :
|
||||||
|
innerSink(innerSink),
|
||||||
|
minLevel(minLevel)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void LevelFilter::receive(const Entry& entry) {
|
||||||
|
if (entry.level >= minLevel) {
|
||||||
|
innerSink->receive(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamSink::StreamSink(shared_ptr<std::ostream> stream, shared_ptr<Formatter> formatter) :
|
||||||
|
stream(stream),
|
||||||
|
formatter(formatter)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void StreamSink::receive(const Entry& entry) {
|
||||||
|
string line = formatter->format(entry);
|
||||||
|
*stream << line << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
StdErrSink::StdErrSink(shared_ptr<Formatter> formatter) :
|
||||||
|
StreamSink(std::shared_ptr<std::ostream>(&std::cerr, [](void*) {}), formatter)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PausableSink::PausableSink(shared_ptr<Sink> innerSink) :
|
||||||
|
innerSink(innerSink)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void PausableSink::receive(const Entry& entry) {
|
||||||
|
lock_guard<std::mutex> lock(mutex);
|
||||||
|
if (isPaused) {
|
||||||
|
buffer.push_back(entry);
|
||||||
|
} else {
|
||||||
|
innerSink->receive(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PausableSink::pause() {
|
||||||
|
lock_guard<std::mutex> lock(mutex);
|
||||||
|
isPaused = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void PausableSink::resume() {
|
||||||
|
lock_guard<std::mutex> lock(mutex);
|
||||||
|
isPaused = false;
|
||||||
|
for (const Entry& entry : buffer) {
|
||||||
|
innerSink->receive(entry);
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Sink.h"
|
||||||
|
#include <memory>
|
||||||
|
#include "Formatter.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace logging {
|
||||||
|
enum class Level;
|
||||||
|
|
||||||
|
class LevelFilter : public Sink {
|
||||||
|
public:
|
||||||
|
LevelFilter(std::shared_ptr<Sink> innerSink, Level minLevel);
|
||||||
|
void receive(const Entry& entry) override;
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Sink> innerSink;
|
||||||
|
Level minLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StreamSink : public Sink {
|
||||||
|
public:
|
||||||
|
StreamSink(std::shared_ptr<std::ostream> stream, std::shared_ptr<Formatter> formatter);
|
||||||
|
void receive(const Entry& entry) override;
|
||||||
|
private:
|
||||||
|
std::shared_ptr<std::ostream> stream;
|
||||||
|
std::shared_ptr<Formatter> formatter;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StdErrSink : public StreamSink {
|
||||||
|
public:
|
||||||
|
explicit StdErrSink(std::shared_ptr<Formatter> formatter);
|
||||||
|
};
|
||||||
|
|
||||||
|
class PausableSink : public Sink {
|
||||||
|
public:
|
||||||
|
explicit PausableSink(std::shared_ptr<Sink> innerSink);
|
||||||
|
void receive(const Entry& entry) override;
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Sink> innerSink;
|
||||||
|
std::vector<Entry> buffer;
|
||||||
|
std::mutex mutex;
|
||||||
|
bool isPaused = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,8 @@
|
||||||
#include "NiceCmdLineOutput.h"
|
#include "NiceCmdLineOutput.h"
|
||||||
#include "ProgressBar.h"
|
#include "ProgressBar.h"
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
|
#include "sinks.h"
|
||||||
|
#include "formatters.h"
|
||||||
#include <gsl_util.h>
|
#include <gsl_util.h>
|
||||||
#include "Exporter.h"
|
#include "Exporter.h"
|
||||||
#include "ContinuousTimeline.h"
|
#include "ContinuousTimeline.h"
|
||||||
|
|
Loading…
Reference in New Issue