Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
S
spdlog
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
spdlog
Commits
9b7812a0
Commit
9b7812a0
authored
Oct 20, 2019
by
gabime
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
auto create log dir
parent
4858d7e4
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
141 additions
and
65 deletions
+141
-65
CMakeLists.txt
bench/CMakeLists.txt
+0
-2
latency.cpp
bench/latency.cpp
+0
-22
meson.build
bench/meson.build
+0
-1
CMakeLists.txt
example/CMakeLists.txt
+0
-2
meson.build
example/meson.build
+0
-1
file_helper-inl.h
include/spdlog/details/file_helper-inl.h
+8
-0
os-inl.h
include/spdlog/details/os-inl.h
+68
-1
os.h
include/spdlog/details/os.h
+11
-0
CMakeLists.txt
tests/CMakeLists.txt
+2
-2
meson.build
tests/meson.build
+0
-4
test_async.cpp
tests/test_async.cpp
+2
-2
test_create_dir.cpp
tests/test_create_dir.cpp
+27
-0
test_daily_logger.cpp
tests/test_daily_logger.cpp
+4
-4
test_errors.cpp
tests/test_errors.cpp
+10
-9
test_file_helper.cpp
tests/test_file_helper.cpp
+1
-1
test_file_logging.cpp
tests/test_file_logging.cpp
+4
-4
test_macros.cpp
tests/test_macros.cpp
+1
-1
utils.cpp
tests/utils.cpp
+3
-9
No files found.
bench/CMakeLists.txt
View file @
9b7812a0
...
...
@@ -24,5 +24,3 @@ target_link_libraries(latency PRIVATE benchmark::benchmark spdlog::spdlog)
add_executable
(
formatter-bench formatter-bench.cpp
)
target_link_libraries
(
formatter-bench PRIVATE benchmark::benchmark spdlog::spdlog
)
file
(
MAKE_DIRECTORY
"
${
CMAKE_CURRENT_BINARY_DIR
}
/logs"
)
bench/latency.cpp
View file @
9b7812a0
...
...
@@ -16,26 +16,6 @@
#include "spdlog/sinks/null_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
void
prepare_logdir
()
{
spdlog
::
info
(
"Preparing latency_logs directory.."
);
#ifdef _WIN32
system
(
"if not exist logs mkdir latency_logs"
);
system
(
"del /F /Q logs
\\
*"
);
#else
auto
rv
=
system
(
"mkdir -p latency_logs"
);
if
(
rv
!=
0
)
{
throw
std
::
runtime_error
(
"Failed to mkdir -p latency_logs"
);
}
rv
=
system
(
"rm -f latency_logs/*"
);
if
(
rv
!=
0
)
{
throw
std
::
runtime_error
(
"Failed to rm -f latency_logs/*"
);
}
#endif
}
void
bench_c_string
(
benchmark
::
State
&
state
,
std
::
shared_ptr
<
spdlog
::
logger
>
logger
)
{
const
char
*
msg
=
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pharetra metus cursus "
...
...
@@ -83,8 +63,6 @@ int main(int argc, char *argv[])
size_t
rotating_files
=
5
;
int
n_threads
=
benchmark
::
CPUInfo
::
Get
().
num_cpus
;
prepare_logdir
();
// disabled loggers
auto
disabled_logger
=
std
::
make_shared
<
spdlog
::
logger
>
(
"bench"
,
std
::
make_shared
<
null_sink_mt
>
());
disabled_logger
->
set_level
(
spdlog
::
level
::
off
);
...
...
bench/meson.build
View file @
9b7812a0
...
...
@@ -12,4 +12,3 @@ foreach i : bench_matrix
benchmark('bench_' + i[0], bench_exe, args: i[2])
endforeach
run_command(find_program('mkdir'), meson.current_build_dir() + '/logs')
example/CMakeLists.txt
View file @
9b7812a0
...
...
@@ -25,5 +25,3 @@ if(SPDLOG_BUILD_EXAMPLE_HO)
target_link_libraries
(
example_header_only PRIVATE spdlog::spdlog_header_only
)
endif
()
# Create logs directory
file
(
MAKE_DIRECTORY
"
${
CMAKE_CURRENT_BINARY_DIR
}
/logs"
)
example/meson.build
View file @
9b7812a0
executable('example', 'example.cpp', dependencies: spdlog_dep)
executable('example_header_only', 'example.cpp', dependencies: spdlog_headeronly_dep)
run_command(find_program('mkdir'), meson.current_build_dir() + '/logs')
include/spdlog/details/file_helper-inl.h
View file @
9b7812a0
...
...
@@ -29,9 +29,16 @@ SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate)
{
close
();
auto
*
mode
=
truncate
?
SPDLOG_FILENAME_T
(
"wb"
)
:
SPDLOG_FILENAME_T
(
"ab"
);
auto
folder_name
=
os
::
dir_name
(
fname
);
_filename
=
fname
;
for
(
int
tries
=
0
;
tries
<
open_tries
;
++
tries
)
{
if
(
!
folder_name
.
empty
())
{
os
::
create_dir
(
folder_name
);
}
if
(
!
os
::
fopen_s
(
&
fd_
,
fname
,
mode
))
{
return
;
...
...
@@ -129,5 +136,6 @@ SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension
// finally - return a valid base and extension tuple
return
std
::
make_tuple
(
fname
.
substr
(
0
,
ext_index
),
fname
.
substr
(
ext_index
));
}
}
// namespace details
}
// namespace spdlog
include/spdlog/details/os-inl.h
View file @
9b7812a0
...
...
@@ -16,6 +16,7 @@
#include <cstring>
#include <ctime>
#include <string>
#include <sstream>
#include <thread>
#include <array>
#include <sys/stat.h>
...
...
@@ -196,7 +197,7 @@ SPDLOG_INLINE bool file_exists(const filename_t &filename) SPDLOG_NOEXCEPT
#else
auto
attribs
=
GetFileAttributesA
(
filename
.
c_str
());
#endif
return
(
attribs
!=
INVALID_FILE_ATTRIBUTES
&&
!
(
attribs
&
FILE_ATTRIBUTE_DIRECTORY
))
;
return
attribs
!=
INVALID_FILE_ATTRIBUTES
;
#else // common linux/unix all have the stat system call
struct
stat
buffer
;
return
(
::
stat
(
filename
.
c_str
(),
&
buffer
)
==
0
);
...
...
@@ -460,6 +461,72 @@ SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target)
}
#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
// return true on success
SPDLOG_INLINE
bool
mkdir_
(
const
filename_t
&
path
)
{
#ifdef _WIN32
#ifdef SPDLOG_WCHAR_FILENAMES
return
::
_wmkdir
(
path
.
c_str
())
==
0
;
#else
return
::
_mkdir
(
path
.
c_str
())
==
0
;
#endif
#else
return
::
mkdir
(
path
.
c_str
(),
mode_t
(
0755
))
==
0
;
#endif
}
// create the given directory - and all directories leading to it
// return true on success
SPDLOG_INLINE
bool
create_dir
(
filename_t
path
)
{
if
(
file_exists
(
path
))
{
return
true
;
}
using
char_type
=
filename_t
::
value_type
;
std
::
basic_istringstream
<
char_type
>
istream
(
path
);
filename_t
token
;
filename_t
cur_dir
;
char_type
sep
=
'/'
;
#ifdef _WIN32
// support forward slash in windows
std
::
replace
(
path
.
begin
(),
path
.
end
(),
char_type
(
'\\'
),
sep
);
#endif
while
(
std
::
getline
(
istream
,
token
,
sep
))
{
if
(
!
token
.
empty
())
{
cur_dir
+=
token
;
if
(
!
file_exists
(
cur_dir
)
&&
!
mkdir_
(
cur_dir
))
{
return
false
;
}
}
cur_dir
+=
sep
;
}
return
true
;
}
// Return directory name from given path or empty string
// "abc/file" => "abc"
// "abc/" => "abc"
// "abc" => ""
// "abc///" => "abc"
SPDLOG_INLINE
filename_t
dir_name
(
filename_t
path
)
{
using
char_type
=
filename_t
::
value_type
;
char_type
sep
=
'/'
;
#ifdef _WIN32
// support forward slash in windows
std
::
replace
(
path
.
begin
(),
path
.
end
(),
char_type
(
'\\'
),
sep
);
#endif
auto
pos
=
path
.
find_last_of
(
sep
);
return
pos
!=
filename_t
::
npos
?
path
.
substr
(
0
,
pos
)
:
filename_t
{};
}
}
// namespace os
}
// namespace details
}
// namespace spdlog
include/spdlog/details/os.h
View file @
9b7812a0
...
...
@@ -89,6 +89,17 @@ bool in_terminal(FILE *file) SPDLOG_NOEXCEPT;
void
wstr_to_utf8buf
(
wstring_view_t
wstr
,
memory_buf_t
&
target
);
#endif
// Return directory name from given path or empty string
// "abc/file" => "abc"
// "abc/" => "abc"
// "abc" => ""
// "abc///" => "abc"
filename_t
dir_name
(
filename_t
path
);
// Create a dir from the given path.
// Return true if succeeded or if this dir already exists.
bool
create_dir
(
filename_t
path
);
}
// namespace os
}
// namespace details
}
// namespace spdlog
...
...
tests/CMakeLists.txt
View file @
9b7812a0
...
...
@@ -25,7 +25,8 @@ set(SPDLOG_UTESTS_SOURCES
test_fmt_helper.cpp
test_stdout_api.cpp
test_dup_filter.cpp
test_backtrace.cpp
)
test_backtrace.cpp
test_create_dir.cpp
)
if
(
NOT SPDLOG_NO_EXCEPTIONS
)
list
(
APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp
)
...
...
@@ -35,7 +36,6 @@ if(systemd_FOUND)
list
(
APPEND SPDLOG_UTESTS_SOURCES test_systemd.cpp
)
endif
()
file
(
MAKE_DIRECTORY
"
${
CMAKE_CURRENT_BINARY_DIR
}
/logs"
)
enable_testing
()
# The compiled library tests
...
...
tests/meson.build
View file @
9b7812a0
...
...
@@ -35,7 +35,6 @@ if systemd_dep.found()
global_test_deps += systemd_dep
endif
run_command('mkdir', 'logs')
# --------------------------------------
# --- Build the test executables ---
# --------------------------------------
...
...
@@ -49,5 +48,3 @@ foreach i : test_matrix
test_exe = executable(i[0], test_sources, dependencies: global_test_deps + [i[1]])
test('test_' + i[0], test_exe)
endforeach
run_command(find_program('mkdir'), meson.current_build_dir() + '/logs')
\ No newline at end of file
tests/test_async.cpp
View file @
9b7812a0
...
...
@@ -157,7 +157,7 @@ TEST_CASE("to_file", "[async]")
prepare_logdir
();
size_t
messages
=
1024
;
size_t
tp_threads
=
1
;
std
::
string
filename
=
"logs/async_test.log"
;
std
::
string
filename
=
"
test_
logs/async_test.log"
;
{
auto
file_sink
=
std
::
make_shared
<
spdlog
::
sinks
::
basic_file_sink_mt
>
(
filename
,
true
);
auto
tp
=
std
::
make_shared
<
spdlog
::
details
::
thread_pool
>
(
messages
,
tp_threads
);
...
...
@@ -179,7 +179,7 @@ TEST_CASE("to_file multi-workers", "[async]")
prepare_logdir
();
size_t
messages
=
1024
*
10
;
size_t
tp_threads
=
10
;
std
::
string
filename
=
"logs/async_test.log"
;
std
::
string
filename
=
"
test_
logs/async_test.log"
;
{
auto
file_sink
=
std
::
make_shared
<
spdlog
::
sinks
::
basic_file_sink_mt
>
(
filename
,
true
);
auto
tp
=
std
::
make_shared
<
spdlog
::
details
::
thread_pool
>
(
messages
,
tp_threads
);
...
...
tests/test_create_dir.cpp
0 → 100644
View file @
9b7812a0
/*
* This content is released under the MIT License as specified in https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE
*/
#include "includes.h"
using
spdlog
::
details
::
os
::
create_dir
;
using
spdlog
::
details
::
os
::
file_exists
;
void
test_create_dir
(
const
char
*
path
,
const
char
*
normalized_path
)
{
auto
rv
=
create_dir
(
path
);
REQUIRE
(
rv
==
true
);
REQUIRE
(
file_exists
(
normalized_path
));
}
#include "spdlog/sinks/stdout_color_sinks.h"
TEST_CASE
(
"create_dir"
,
"[create_dir]"
)
{
prepare_logdir
();
test_create_dir
(
"test_logs/dir1/dir1"
,
"test_logs/dir1/dir1"
);
test_create_dir
(
"test_logs/dir1///dir2"
,
"test_logs/dir1/dir2"
);
test_create_dir
(
"./test_logs/dir1/dir3"
,
"test_logs/dir1/dir3"
);
test_create_dir
(
"test_logs/../test_logs/dir1/dir4"
,
"test_logs/dir1/dir4"
);
test_create_dir
(
"./test_logs/dir1/dir2/dir99/../dir23"
,
"test_logs/dir1/dir2/dir23"
);
spdlog
::
drop
(
"test-create-dir"
);
}
tests/test_daily_logger.cpp
View file @
9b7812a0
...
...
@@ -10,7 +10,7 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
prepare_logdir
();
// calculate filename (time based)
std
::
string
basename
=
"logs/daily_dateonly"
;
std
::
string
basename
=
"
test_
logs/daily_dateonly"
;
std
::
tm
tm
=
spdlog
::
details
::
os
::
localtime
();
spdlog
::
memory_buf_t
w
;
fmt
::
format_to
(
w
,
"{}_{:04d}-{:02d}-{:02d}"
,
basename
,
tm
.
tm_year
+
1900
,
tm
.
tm_mon
+
1
,
tm
.
tm_mday
);
...
...
@@ -44,7 +44,7 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
prepare_logdir
();
// calculate filename (time based)
std
::
string
basename
=
"logs/daily_dateonly"
;
std
::
string
basename
=
"
test_
logs/daily_dateonly"
;
std
::
tm
tm
=
spdlog
::
details
::
os
::
localtime
();
spdlog
::
memory_buf_t
w
;
fmt
::
format_to
(
w
,
"{}{:04d}{:02d}{:02d}"
,
basename
,
tm
.
tm_year
+
1900
,
tm
.
tm_mon
+
1
,
tm
.
tm_mday
);
...
...
@@ -119,7 +119,7 @@ static void test_rotate(int days_to_run, uint16_t max_days, uint16_t expected_n_
prepare_logdir
();
std
::
string
basename
=
"logs/daily_rotate.txt"
;
std
::
string
basename
=
"
test_
logs/daily_rotate.txt"
;
daily_file_sink_st
sink
{
basename
,
2
,
30
,
true
,
max_days
};
// simulate messages with 24 intervals
...
...
@@ -130,7 +130,7 @@ static void test_rotate(int days_to_run, uint16_t max_days, uint16_t expected_n_
sink
.
log
(
create_msg
(
offset
));
}
REQUIRE
(
count_files
(
"logs"
)
==
static_cast
<
size_t
>
(
expected_n_files
));
REQUIRE
(
count_files
(
"
test_
logs"
)
==
static_cast
<
size_t
>
(
expected_n_files
));
}
TEST_CASE
(
"daily_logger rotate"
,
"[daily_file_sink]"
)
...
...
tests/test_errors.cpp
View file @
9b7812a0
...
...
@@ -26,7 +26,7 @@ protected:
TEST_CASE
(
"default_error_handler"
,
"[errors]]"
)
{
prepare_logdir
();
std
::
string
filename
=
"logs/simple_log.txt"
;
std
::
string
filename
=
"
test_
logs/simple_log.txt"
;
auto
logger
=
spdlog
::
create
<
spdlog
::
sinks
::
basic_file_sink_mt
>
(
"test-error"
,
filename
,
true
);
logger
->
set_pattern
(
"%v"
);
...
...
@@ -43,7 +43,7 @@ struct custom_ex
TEST_CASE
(
"custom_error_handler"
,
"[errors]]"
)
{
prepare_logdir
();
std
::
string
filename
=
"logs/simple_log.txt"
;
std
::
string
filename
=
"
test_
logs/simple_log.txt"
;
auto
logger
=
spdlog
::
create
<
spdlog
::
sinks
::
basic_file_sink_mt
>
(
"logger"
,
filename
,
true
);
logger
->
flush_on
(
spdlog
::
level
::
info
);
logger
->
set_error_handler
([
=
](
const
std
::
string
&
)
{
throw
custom_ex
();
});
...
...
@@ -75,15 +75,15 @@ TEST_CASE("async_error_handler", "[errors]]")
prepare_logdir
();
std
::
string
err_msg
(
"log failed with some msg"
);
std
::
string
filename
=
"logs/simple_async_log.txt"
;
std
::
string
filename
=
"
test_
logs/simple_async_log.txt"
;
{
spdlog
::
init_thread_pool
(
128
,
1
);
auto
logger
=
spdlog
::
create_async
<
spdlog
::
sinks
::
basic_file_sink_mt
>
(
"logger"
,
filename
,
true
);
logger
->
set_error_handler
([
=
](
const
std
::
string
&
)
{
std
::
ofstream
ofs
(
"logs/custom_err.txt"
);
std
::
ofstream
ofs
(
"
test_
logs/custom_err.txt"
);
if
(
!
ofs
)
{
throw
std
::
runtime_error
(
"Failed open logs/custom_err.txt"
);
throw
std
::
runtime_error
(
"Failed open
test_
logs/custom_err.txt"
);
}
ofs
<<
err_msg
;
});
...
...
@@ -94,7 +94,7 @@ TEST_CASE("async_error_handler", "[errors]]")
}
spdlog
::
init_thread_pool
(
128
,
1
);
REQUIRE
(
count_lines
(
filename
)
==
2
);
REQUIRE
(
file_contents
(
"logs/custom_err.txt"
)
==
err_msg
);
REQUIRE
(
file_contents
(
"
test_
logs/custom_err.txt"
)
==
err_msg
);
}
// Make sure async error handler is executed
...
...
@@ -103,12 +103,13 @@ TEST_CASE("async_error_handler2", "[errors]]")
prepare_logdir
();
std
::
string
err_msg
(
"This is async handler error message"
);
{
spdlog
::
details
::
os
::
create_dir
(
"test_logs"
);
spdlog
::
init_thread_pool
(
128
,
1
);
auto
logger
=
spdlog
::
create_async
<
failing_sink
>
(
"failed_logger"
);
logger
->
set_error_handler
([
=
](
const
std
::
string
&
)
{
std
::
ofstream
ofs
(
"logs/custom_err2.txt"
);
std
::
ofstream
ofs
(
"
test_
logs/custom_err2.txt"
);
if
(
!
ofs
)
throw
std
::
runtime_error
(
"Failed open logs/custom_err2.txt"
);
throw
std
::
runtime_error
(
"Failed open
test_
logs/custom_err2.txt"
);
ofs
<<
err_msg
;
});
logger
->
info
(
"Hello failure"
);
...
...
@@ -116,5 +117,5 @@ TEST_CASE("async_error_handler2", "[errors]]")
}
spdlog
::
init_thread_pool
(
128
,
1
);
REQUIRE
(
file_contents
(
"logs/custom_err2.txt"
)
==
err_msg
);
REQUIRE
(
file_contents
(
"
test_
logs/custom_err2.txt"
)
==
err_msg
);
}
tests/test_file_helper.cpp
View file @
9b7812a0
...
...
@@ -6,7 +6,7 @@
using
spdlog
::
details
::
file_helper
;
using
spdlog
::
details
::
log_msg
;
static
const
std
::
string
target_filename
=
"logs/file_helper_test.txt"
;
static
const
std
::
string
target_filename
=
"
test_
logs/file_helper_test.txt"
;
static
void
write_with_helper
(
file_helper
&
helper
,
size_t
howmany
)
{
...
...
tests/test_file_logging.cpp
View file @
9b7812a0
...
...
@@ -6,7 +6,7 @@
TEST_CASE
(
"simple_file_logger"
,
"[simple_logger]]"
)
{
prepare_logdir
();
std
::
string
filename
=
"logs/simple_log"
;
std
::
string
filename
=
"
test_
logs/simple_log"
;
auto
logger
=
spdlog
::
create
<
spdlog
::
sinks
::
basic_file_sink_mt
>
(
"logger"
,
filename
);
logger
->
set_pattern
(
"%v"
);
...
...
@@ -22,7 +22,7 @@ TEST_CASE("simple_file_logger", "[simple_logger]]")
TEST_CASE
(
"flush_on"
,
"[flush_on]]"
)
{
prepare_logdir
();
std
::
string
filename
=
"logs/simple_log"
;
std
::
string
filename
=
"
test_
logs/simple_log"
;
auto
logger
=
spdlog
::
create
<
spdlog
::
sinks
::
basic_file_sink_mt
>
(
"logger"
,
filename
);
logger
->
set_pattern
(
"%v"
);
...
...
@@ -42,7 +42,7 @@ TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
{
prepare_logdir
();
size_t
max_size
=
1024
*
10
;
std
::
string
basename
=
"logs/rotating_log"
;
std
::
string
basename
=
"
test_
logs/rotating_log"
;
auto
logger
=
spdlog
::
rotating_logger_mt
(
"logger"
,
basename
,
max_size
,
0
);
for
(
int
i
=
0
;
i
<
10
;
++
i
)
...
...
@@ -59,7 +59,7 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
{
prepare_logdir
();
size_t
max_size
=
1024
*
10
;
std
::
string
basename
=
"logs/rotating_log"
;
std
::
string
basename
=
"
test_
logs/rotating_log"
;
{
// make an initial logger to create the first output file
...
...
tests/test_macros.cpp
View file @
9b7812a0
...
...
@@ -12,7 +12,7 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
{
prepare_logdir
();
std
::
string
filename
=
"logs/simple_log"
;
std
::
string
filename
=
"
test_
logs/simple_log"
;
auto
logger
=
spdlog
::
create
<
spdlog
::
sinks
::
basic_file_sink_mt
>
(
"logger"
,
filename
);
logger
->
set_pattern
(
"%v"
);
...
...
tests/utils.cpp
View file @
9b7812a0
...
...
@@ -9,18 +9,12 @@ void prepare_logdir()
{
spdlog
::
drop_all
();
#ifdef _WIN32
system
(
"if not exist logs mkdir logs"
);
system
(
"del /F /Q logs
\\
*"
);
system
(
"rmdir /S /Q test_logs"
)
#else
auto
rv
=
system
(
"
mkdir -p
logs"
);
auto
rv
=
system
(
"
rm -rf test_
logs"
);
if
(
rv
!=
0
)
{
throw
std
::
runtime_error
(
"Failed to mkdir -p logs"
);
}
rv
=
system
(
"rm -f logs/*"
);
if
(
rv
!=
0
)
{
throw
std
::
runtime_error
(
"Failed to rm -f logs/*"
);
throw
std
::
runtime_error
(
"Failed to rm -rf test_logs"
);
}
#endif
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment