mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-22 10:08:15 -05:00
reorg common/log
This commit is contained in:
parent
a37819c330
commit
abdcda0a2f
@ -1,8 +1,9 @@
|
|||||||
package alloc
|
package alloc_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/v2ray/v2ray-core/common/alloc"
|
||||||
v2testing "github.com/v2ray/v2ray-core/testing"
|
v2testing "github.com/v2ray/v2ray-core/testing"
|
||||||
"github.com/v2ray/v2ray-core/testing/assert"
|
"github.com/v2ray/v2ray-core/testing/assert"
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/v2ray/v2ray-core/common/alloc"
|
"github.com/v2ray/v2ray-core/common/log/internal"
|
||||||
"github.com/v2ray/v2ray-core/common/serial"
|
"github.com/v2ray/v2ray-core/common/serial"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,32 +14,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
accessLoggerInstance logWriter = &noOpLogWriter{}
|
accessLoggerInstance internal.LogWriter = new(internal.NoOpLogWriter)
|
||||||
)
|
)
|
||||||
|
|
||||||
type accessLog struct {
|
|
||||||
From serial.String
|
|
||||||
To serial.String
|
|
||||||
Status AccessStatus
|
|
||||||
Reason serial.String
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *accessLog) Release() {
|
|
||||||
this.From = nil
|
|
||||||
this.To = nil
|
|
||||||
this.Reason = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *accessLog) String() string {
|
|
||||||
b := alloc.NewSmallBuffer().Clear()
|
|
||||||
defer b.Release()
|
|
||||||
|
|
||||||
return b.AppendString(this.From.String()).AppendString(" ").AppendString(string(this.Status)).AppendString(" ").AppendString(this.To.String()).AppendString(" ").AppendString(this.Reason.String()).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitAccessLogger initializes the access logger to write into the give file.
|
// InitAccessLogger initializes the access logger to write into the give file.
|
||||||
func InitAccessLogger(file string) error {
|
func InitAccessLogger(file string) error {
|
||||||
logger, err := newFileLogWriter(file)
|
logger, err := internal.NewFileLogWriter(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Failed to create access logger on file (", file, "): ", file, err)
|
Error("Failed to create access logger on file (", file, "): ", file, err)
|
||||||
return err
|
return err
|
||||||
@ -50,10 +30,10 @@ func InitAccessLogger(file string) error {
|
|||||||
|
|
||||||
// Access writes an access log.
|
// Access writes an access log.
|
||||||
func Access(from, to serial.String, status AccessStatus, reason serial.String) {
|
func Access(from, to serial.String, status AccessStatus, reason serial.String) {
|
||||||
accessLoggerInstance.Log(&accessLog{
|
accessLoggerInstance.Log(&internal.AccessLog{
|
||||||
From: from,
|
From: from,
|
||||||
To: to,
|
To: to,
|
||||||
Status: status,
|
Status: string(status),
|
||||||
Reason: reason,
|
Reason: reason,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/v2ray/v2ray-core/common/serial"
|
|
||||||
v2testing "github.com/v2ray/v2ray-core/testing"
|
|
||||||
"github.com/v2ray/v2ray-core/testing/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAccessLog(t *testing.T) {
|
|
||||||
v2testing.Current(t)
|
|
||||||
|
|
||||||
filename := "/tmp/test_access_log.log"
|
|
||||||
InitAccessLogger(filename)
|
|
||||||
_, err := os.Stat(filename)
|
|
||||||
assert.Error(err).IsNil()
|
|
||||||
|
|
||||||
Access(serial.StringLiteral("test_from"), serial.StringLiteral("test_to"), AccessAccepted, serial.StringLiteral("test_reason"))
|
|
||||||
<-time.After(2 * time.Second)
|
|
||||||
|
|
||||||
accessLoggerInstance.(*fileLogWriter).close()
|
|
||||||
accessLoggerInstance = &noOpLogWriter{}
|
|
||||||
|
|
||||||
content, err := ioutil.ReadFile(filename)
|
|
||||||
assert.Error(err).IsNil()
|
|
||||||
|
|
||||||
contentStr := serial.StringLiteral(content)
|
|
||||||
assert.String(contentStr).Contains(serial.StringLiteral("test_from"))
|
|
||||||
assert.String(contentStr).Contains(serial.StringLiteral("test_to"))
|
|
||||||
assert.String(contentStr).Contains(serial.StringLiteral("test_reason"))
|
|
||||||
assert.String(contentStr).Contains(serial.StringLiteral("accepted"))
|
|
||||||
}
|
|
69
common/log/internal/log_entry.go
Normal file
69
common/log/internal/log_entry.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/v2ray/v2ray-core/common"
|
||||||
|
"github.com/v2ray/v2ray-core/common/alloc"
|
||||||
|
"github.com/v2ray/v2ray-core/common/serial"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogEntry interface {
|
||||||
|
common.Releasable
|
||||||
|
serial.String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorLog struct {
|
||||||
|
Prefix string
|
||||||
|
Values []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ErrorLog) Release() {
|
||||||
|
for index := range this.Values {
|
||||||
|
this.Values[index] = nil
|
||||||
|
}
|
||||||
|
this.Values = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *ErrorLog) String() string {
|
||||||
|
b := alloc.NewSmallBuffer().Clear()
|
||||||
|
defer b.Release()
|
||||||
|
|
||||||
|
b.AppendString(this.Prefix)
|
||||||
|
|
||||||
|
for _, value := range this.Values {
|
||||||
|
switch typedVal := value.(type) {
|
||||||
|
case string:
|
||||||
|
b.AppendString(typedVal)
|
||||||
|
case *string:
|
||||||
|
b.AppendString(*typedVal)
|
||||||
|
case serial.String:
|
||||||
|
b.AppendString(typedVal.String())
|
||||||
|
case error:
|
||||||
|
b.AppendString(typedVal.Error())
|
||||||
|
default:
|
||||||
|
b.AppendString(fmt.Sprint(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessLog struct {
|
||||||
|
From serial.String
|
||||||
|
To serial.String
|
||||||
|
Status string
|
||||||
|
Reason serial.String
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *AccessLog) Release() {
|
||||||
|
this.From = nil
|
||||||
|
this.To = nil
|
||||||
|
this.Reason = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *AccessLog) String() string {
|
||||||
|
b := alloc.NewSmallBuffer().Clear()
|
||||||
|
defer b.Release()
|
||||||
|
|
||||||
|
return b.AppendString(this.From.String()).AppendString(" ").AppendString(this.Status).AppendString(" ").AppendString(this.To.String()).AppendString(" ").AppendString(this.Reason.String()).String()
|
||||||
|
}
|
27
common/log/internal/log_entry_test.go
Normal file
27
common/log/internal/log_entry_test.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package internal_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/v2ray/v2ray-core/common/log/internal"
|
||||||
|
"github.com/v2ray/v2ray-core/common/serial"
|
||||||
|
v2testing "github.com/v2ray/v2ray-core/testing"
|
||||||
|
"github.com/v2ray/v2ray-core/testing/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessLog(t *testing.T) {
|
||||||
|
v2testing.Current(t)
|
||||||
|
|
||||||
|
entry := &AccessLog{
|
||||||
|
From: serial.StringLiteral("test_from"),
|
||||||
|
To: serial.StringLiteral("test_to"),
|
||||||
|
Status: "Accepted",
|
||||||
|
Reason: serial.StringLiteral("test_reason"),
|
||||||
|
}
|
||||||
|
|
||||||
|
entryStr := entry.String()
|
||||||
|
assert.StringLiteral(entryStr).Contains(serial.StringLiteral("test_from"))
|
||||||
|
assert.StringLiteral(entryStr).Contains(serial.StringLiteral("test_to"))
|
||||||
|
assert.StringLiteral(entryStr).Contains(serial.StringLiteral("test_reason"))
|
||||||
|
assert.StringLiteral(entryStr).Contains(serial.StringLiteral("Accepted"))
|
||||||
|
}
|
@ -1,50 +1,45 @@
|
|||||||
package log
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/v2ray/v2ray-core/common/platform"
|
"github.com/v2ray/v2ray-core/common/platform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createLogger(writer io.Writer) *log.Logger {
|
type LogWriter interface {
|
||||||
return log.New(writer, "", log.Ldate|log.Ltime)
|
|
||||||
}
|
|
||||||
|
|
||||||
type logWriter interface {
|
|
||||||
Log(LogEntry)
|
Log(LogEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
type noOpLogWriter struct {
|
type NoOpLogWriter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *noOpLogWriter) Log(entry LogEntry) {
|
func (this *NoOpLogWriter) Log(entry LogEntry) {
|
||||||
entry.Release()
|
entry.Release()
|
||||||
}
|
}
|
||||||
|
|
||||||
type stdOutLogWriter struct {
|
type StdOutLogWriter struct {
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStdOutLogWriter() logWriter {
|
func NewStdOutLogWriter() LogWriter {
|
||||||
return &stdOutLogWriter{
|
return &StdOutLogWriter{
|
||||||
logger: createLogger(os.Stdout),
|
logger: log.New(os.Stdout, "", log.Ldate|log.Ltime),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *stdOutLogWriter) Log(log LogEntry) {
|
func (this *StdOutLogWriter) Log(log LogEntry) {
|
||||||
this.logger.Print(log.String() + platform.LineSeparator())
|
this.logger.Print(log.String() + platform.LineSeparator())
|
||||||
log.Release()
|
log.Release()
|
||||||
}
|
}
|
||||||
|
|
||||||
type fileLogWriter struct {
|
type FileLogWriter struct {
|
||||||
queue chan LogEntry
|
queue chan LogEntry
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
file *os.File
|
file *os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *fileLogWriter) Log(log LogEntry) {
|
func (this *FileLogWriter) Log(log LogEntry) {
|
||||||
select {
|
select {
|
||||||
case this.queue <- log:
|
case this.queue <- log:
|
||||||
default:
|
default:
|
||||||
@ -53,7 +48,7 @@ func (this *fileLogWriter) Log(log LogEntry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *fileLogWriter) run() {
|
func (this *FileLogWriter) run() {
|
||||||
for {
|
for {
|
||||||
entry, open := <-this.queue
|
entry, open := <-this.queue
|
||||||
if !open {
|
if !open {
|
||||||
@ -65,16 +60,16 @@ func (this *fileLogWriter) run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *fileLogWriter) close() {
|
func (this *FileLogWriter) Close() {
|
||||||
this.file.Close()
|
this.file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFileLogWriter(path string) (*fileLogWriter, error) {
|
func NewFileLogWriter(path string) (*FileLogWriter, error) {
|
||||||
file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
file, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logger := &fileLogWriter{
|
logger := &FileLogWriter{
|
||||||
queue: make(chan LogEntry, 16),
|
queue: make(chan LogEntry, 16),
|
||||||
logger: log.New(file, "", log.Ldate|log.Ltime),
|
logger: log.New(file, "", log.Ldate|log.Ltime),
|
||||||
file: file,
|
file: file,
|
1
common/log/internal/log_writer_test.go
Normal file
1
common/log/internal/log_writer_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package internal_test
|
@ -1,13 +1,11 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/v2ray/v2ray-core/common/log/internal"
|
||||||
|
|
||||||
"github.com/v2ray/v2ray-core/common"
|
|
||||||
"github.com/v2ray/v2ray-core/common/alloc"
|
|
||||||
"github.com/v2ray/v2ray-core/common/serial"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type LogLevel int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DebugLevel = LogLevel(0)
|
DebugLevel = LogLevel(0)
|
||||||
InfoLevel = LogLevel(1)
|
InfoLevel = LogLevel(1)
|
||||||
@ -16,86 +14,43 @@ const (
|
|||||||
NoneLevel = LogLevel(999)
|
NoneLevel = LogLevel(999)
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogEntry interface {
|
|
||||||
common.Releasable
|
|
||||||
serial.String
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorLog struct {
|
|
||||||
prefix string
|
|
||||||
values []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *errorLog) Release() {
|
|
||||||
for index := range this.values {
|
|
||||||
this.values[index] = nil
|
|
||||||
}
|
|
||||||
this.values = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *errorLog) String() string {
|
|
||||||
b := alloc.NewSmallBuffer().Clear()
|
|
||||||
defer b.Release()
|
|
||||||
|
|
||||||
b.AppendString(this.prefix)
|
|
||||||
|
|
||||||
for _, value := range this.values {
|
|
||||||
switch typedVal := value.(type) {
|
|
||||||
case string:
|
|
||||||
b.AppendString(typedVal)
|
|
||||||
case *string:
|
|
||||||
b.AppendString(*typedVal)
|
|
||||||
case serial.String:
|
|
||||||
b.AppendString(typedVal.String())
|
|
||||||
case error:
|
|
||||||
b.AppendString(typedVal.Error())
|
|
||||||
default:
|
|
||||||
b.AppendString(fmt.Sprint(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
noOpLoggerInstance logWriter = &noOpLogWriter{}
|
streamLoggerInstance internal.LogWriter = internal.NewStdOutLogWriter()
|
||||||
streamLoggerInstance logWriter = newStdOutLogWriter()
|
|
||||||
|
|
||||||
debugLogger = noOpLoggerInstance
|
debugLogger internal.LogWriter = streamLoggerInstance
|
||||||
infoLogger = noOpLoggerInstance
|
infoLogger internal.LogWriter = streamLoggerInstance
|
||||||
warningLogger = streamLoggerInstance
|
warningLogger internal.LogWriter = streamLoggerInstance
|
||||||
errorLogger = streamLoggerInstance
|
errorLogger internal.LogWriter = streamLoggerInstance
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogLevel int
|
|
||||||
|
|
||||||
func SetLogLevel(level LogLevel) {
|
func SetLogLevel(level LogLevel) {
|
||||||
debugLogger = noOpLoggerInstance
|
debugLogger = new(internal.NoOpLogWriter)
|
||||||
if level <= DebugLevel {
|
if level <= DebugLevel {
|
||||||
debugLogger = streamLoggerInstance
|
debugLogger = streamLoggerInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
infoLogger = noOpLoggerInstance
|
infoLogger = new(internal.NoOpLogWriter)
|
||||||
if level <= InfoLevel {
|
if level <= InfoLevel {
|
||||||
infoLogger = streamLoggerInstance
|
infoLogger = streamLoggerInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
warningLogger = noOpLoggerInstance
|
warningLogger = new(internal.NoOpLogWriter)
|
||||||
if level <= WarningLevel {
|
if level <= WarningLevel {
|
||||||
warningLogger = streamLoggerInstance
|
warningLogger = streamLoggerInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
errorLogger = noOpLoggerInstance
|
errorLogger = new(internal.NoOpLogWriter)
|
||||||
if level <= ErrorLevel {
|
if level <= ErrorLevel {
|
||||||
errorLogger = streamLoggerInstance
|
errorLogger = streamLoggerInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
if level == NoneLevel {
|
if level == NoneLevel {
|
||||||
accessLoggerInstance = noOpLoggerInstance
|
accessLoggerInstance = new(internal.NoOpLogWriter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitErrorLogger(file string) error {
|
func InitErrorLogger(file string) error {
|
||||||
logger, err := newFileLogWriter(file)
|
logger, err := internal.NewFileLogWriter(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Failed to create error logger on file (", file, "): ", err)
|
Error("Failed to create error logger on file (", file, "): ", err)
|
||||||
return err
|
return err
|
||||||
@ -106,32 +61,32 @@ func InitErrorLogger(file string) error {
|
|||||||
|
|
||||||
// Debug outputs a debug log with given format and optional arguments.
|
// Debug outputs a debug log with given format and optional arguments.
|
||||||
func Debug(v ...interface{}) {
|
func Debug(v ...interface{}) {
|
||||||
debugLogger.Log(&errorLog{
|
debugLogger.Log(&internal.ErrorLog{
|
||||||
prefix: "[Debug]",
|
Prefix: "[Debug]",
|
||||||
values: v,
|
Values: v,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info outputs an info log with given format and optional arguments.
|
// Info outputs an info log with given format and optional arguments.
|
||||||
func Info(v ...interface{}) {
|
func Info(v ...interface{}) {
|
||||||
infoLogger.Log(&errorLog{
|
infoLogger.Log(&internal.ErrorLog{
|
||||||
prefix: "[Info]",
|
Prefix: "[Info]",
|
||||||
values: v,
|
Values: v,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning outputs a warning log with given format and optional arguments.
|
// Warning outputs a warning log with given format and optional arguments.
|
||||||
func Warning(v ...interface{}) {
|
func Warning(v ...interface{}) {
|
||||||
warningLogger.Log(&errorLog{
|
warningLogger.Log(&internal.ErrorLog{
|
||||||
prefix: "[Warning]",
|
Prefix: "[Warning]",
|
||||||
values: v,
|
Values: v,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error outputs an error log with given format and optional arguments.
|
// Error outputs an error log with given format and optional arguments.
|
||||||
func Error(v ...interface{}) {
|
func Error(v ...interface{}) {
|
||||||
errorLogger.Log(&errorLog{
|
errorLogger.Log(&internal.ErrorLog{
|
||||||
prefix: "[Error]",
|
Prefix: "[Error]",
|
||||||
values: v,
|
Values: v,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/v2ray/v2ray-core/common/platform"
|
|
||||||
"github.com/v2ray/v2ray-core/common/serial"
|
|
||||||
v2testing "github.com/v2ray/v2ray-core/testing"
|
|
||||||
"github.com/v2ray/v2ray-core/testing/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLogLevelSetting(t *testing.T) {
|
|
||||||
v2testing.Current(t)
|
|
||||||
|
|
||||||
assert.Pointer(debugLogger).Equals(noOpLoggerInstance)
|
|
||||||
SetLogLevel(DebugLevel)
|
|
||||||
assert.Pointer(debugLogger).Equals(streamLoggerInstance)
|
|
||||||
|
|
||||||
SetLogLevel(InfoLevel)
|
|
||||||
assert.Pointer(debugLogger).Equals(noOpLoggerInstance)
|
|
||||||
assert.Pointer(infoLogger).Equals(streamLoggerInstance)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStreamLogger(t *testing.T) {
|
|
||||||
v2testing.Current(t)
|
|
||||||
|
|
||||||
buffer := bytes.NewBuffer(make([]byte, 0, 1024))
|
|
||||||
infoLogger = &stdOutLogWriter{
|
|
||||||
logger: log.New(buffer, "", 0),
|
|
||||||
}
|
|
||||||
Info("Test ", "Stream Logger", " Format")
|
|
||||||
assert.StringLiteral(string(buffer.Bytes())).Equals("[Info]Test Stream Logger Format" + platform.LineSeparator())
|
|
||||||
|
|
||||||
buffer.Reset()
|
|
||||||
errorLogger = infoLogger
|
|
||||||
Error("Test ", serial.StringLiteral("literal"), " Format")
|
|
||||||
assert.StringLiteral(string(buffer.Bytes())).Equals("[Error]Test literal Format" + platform.LineSeparator())
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user